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 gameInfo.boardWidth = newWidth;
2450 gameInfo.boardHeight = newHeight;
2451 gameInfo.holdingsWidth = newHoldingsWidth;
2452 gameInfo.variant = newVariant;
2453 InitDrawingSizes(-2, 0);
2454 } else gameInfo.variant = newVariant;
2455 CopyBoard(oldBoard, board); // remember correctly formatted board
2456 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2457 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 static int loggedOn = FALSE;
2462 /*-- Game start info cache: --*/
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\ ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2492 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494 if(r < minRating+100 && r >=0 ) r = minRating+100;
2495 if(r > maxRating) r = maxRating;
2496 if(tc < 1.) tc = 1.;
2497 if(tc > 95.) tc = 95.;
2498 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499 y = ((double)r - minRating)/(maxRating - minRating)
2500 * (h-vMargin-squareSize/8-1) + vMargin;
2501 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502 if(strstr(seekAdList[i], " u ")) color = 1;
2503 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504 !strstr(seekAdList[i], "bullet") &&
2505 !strstr(seekAdList[i], "blitz") &&
2506 !strstr(seekAdList[i], "standard") ) color = 2;
2507 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2512 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2514 char buf[MSG_SIZ], *ext = "";
2515 VariantClass v = StringToVariant(type);
2516 if(strstr(type, "wild")) {
2517 ext = type + 4; // append wild number
2518 if(v == VariantFischeRandom) type = "chess960"; else
2519 if(v == VariantLoadable) type = "setup"; else
2520 type = VariantName(v);
2522 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528 seekNrList[nrOfSeekAds] = nr;
2529 zList[nrOfSeekAds] = 0;
2530 seekAdList[nrOfSeekAds++] = StrSave(buf);
2531 if(plot) PlotSeekAd(nrOfSeekAds-1);
2536 EraseSeekDot (int i)
2538 int x = xList[i], y = yList[i], d=squareSize/4, k;
2539 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541 // now replot every dot that overlapped
2542 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543 int xx = xList[k], yy = yList[k];
2544 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545 DrawSeekDot(xx, yy, colorList[k]);
2550 RemoveSeekAd (int nr)
2553 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555 if(seekAdList[i]) free(seekAdList[i]);
2556 seekAdList[i] = seekAdList[--nrOfSeekAds];
2557 seekNrList[i] = seekNrList[nrOfSeekAds];
2558 ratingList[i] = ratingList[nrOfSeekAds];
2559 colorList[i] = colorList[nrOfSeekAds];
2560 tcList[i] = tcList[nrOfSeekAds];
2561 xList[i] = xList[nrOfSeekAds];
2562 yList[i] = yList[nrOfSeekAds];
2563 zList[i] = zList[nrOfSeekAds];
2564 seekAdList[nrOfSeekAds] = NULL;
2570 MatchSoughtLine (char *line)
2572 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573 int nr, base, inc, u=0; char dummy;
2575 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2580 // match: compact and save the line
2581 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2591 if(!seekGraphUp) return FALSE;
2592 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2595 DrawSeekBackground(0, 0, w, h);
2596 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604 snprintf(buf, MSG_SIZ, "%d", i);
2605 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609 for(i=1; i<100; i+=(i<10?1:5)) {
2610 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614 snprintf(buf, MSG_SIZ, "%d", i);
2615 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 static int lastDown = 0, displayed = 0, lastSecond;
2626 if(y < 0) return FALSE;
2627 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629 if(!seekGraphUp) return FALSE;
2630 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631 DrawPosition(TRUE, NULL);
2634 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635 if(click == Release || moving) return FALSE;
2637 soughtPending = TRUE;
2638 SendToICS(ics_prefix);
2639 SendToICS("sought\n"); // should this be "sought all"?
2640 } else { // issue challenge based on clicked ad
2641 int dist = 10000; int i, closest = 0, second = 0;
2642 for(i=0; i<nrOfSeekAds; i++) {
2643 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2644 if(d < dist) { dist = d; closest = i; }
2645 second += (d - zList[i] < 120); // count in-range ads
2646 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2650 second = (second > 1);
2651 if(displayed != closest || second != lastSecond) {
2652 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653 lastSecond = second; displayed = closest;
2655 if(click == Press) {
2656 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659 } // on press 'hit', only show info
2660 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662 SendToICS(ics_prefix);
2664 return TRUE; // let incoming board of started game pop down the graph
2665 } else if(click == Release) { // release 'miss' is ignored
2666 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667 if(moving == 2) { // right up-click
2668 nrOfSeekAds = 0; // refresh graph
2669 soughtPending = TRUE;
2670 SendToICS(ics_prefix);
2671 SendToICS("sought\n"); // should this be "sought all"?
2674 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675 // press miss or release hit 'pop down' seek graph
2676 seekGraphUp = FALSE;
2677 DrawPosition(TRUE, NULL);
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2695 static int started = STARTED_NONE;
2696 static char parse[20000];
2697 static int parse_pos = 0;
2698 static char buf[BUF_SIZE + 1];
2699 static int firstTime = TRUE, intfSet = FALSE;
2700 static ColorClass prevColor = ColorNormal;
2701 static int savingComment = FALSE;
2702 static int cmatch = 0; // continuation sequence match
2709 int backup; /* [DM] For zippy color lines */
2711 char talker[MSG_SIZ]; // [HGM] chat
2714 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716 if (appData.debugMode) {
2718 fprintf(debugFP, "<ICS: ");
2719 show_bytes(debugFP, data, count);
2720 fprintf(debugFP, "\n");
2724 if (appData.debugMode) { int f = forwardMostMove;
2725 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730 /* If last read ended with a partial line that we couldn't parse,
2731 prepend it to the new read and try again. */
2732 if (leftover_len > 0) {
2733 for (i=0; i<leftover_len; i++)
2734 buf[i] = buf[leftover_start + i];
2737 /* copy new characters into the buffer */
2738 bp = buf + leftover_len;
2739 buf_len=leftover_len;
2740 for (i=0; i<count; i++)
2743 if (data[i] == '\r')
2746 // join lines split by ICS?
2747 if (!appData.noJoin)
2750 Joining just consists of finding matches against the
2751 continuation sequence, and discarding that sequence
2752 if found instead of copying it. So, until a match
2753 fails, there's nothing to do since it might be the
2754 complete sequence, and thus, something we don't want
2757 if (data[i] == cont_seq[cmatch])
2760 if (cmatch == strlen(cont_seq))
2762 cmatch = 0; // complete match. just reset the counter
2765 it's possible for the ICS to not include the space
2766 at the end of the last word, making our [correct]
2767 join operation fuse two separate words. the server
2768 does this when the space occurs at the width setting.
2770 if (!buf_len || buf[buf_len-1] != ' ')
2781 match failed, so we have to copy what matched before
2782 falling through and copying this character. In reality,
2783 this will only ever be just the newline character, but
2784 it doesn't hurt to be precise.
2786 strncpy(bp, cont_seq, cmatch);
2798 buf[buf_len] = NULLCHAR;
2799 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2804 while (i < buf_len) {
2805 /* Deal with part of the TELNET option negotiation
2806 protocol. We refuse to do anything beyond the
2807 defaults, except that we allow the WILL ECHO option,
2808 which ICS uses to turn off password echoing when we are
2809 directly connected to it. We reject this option
2810 if localLineEditing mode is on (always on in xboard)
2811 and we are talking to port 23, which might be a real
2812 telnet server that will try to keep WILL ECHO on permanently.
2814 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816 unsigned char option;
2818 switch ((unsigned char) buf[++i]) {
2820 if (appData.debugMode)
2821 fprintf(debugFP, "\n<WILL ");
2822 switch (option = (unsigned char) buf[++i]) {
2824 if (appData.debugMode)
2825 fprintf(debugFP, "ECHO ");
2826 /* Reply only if this is a change, according
2827 to the protocol rules. */
2828 if (remoteEchoOption) break;
2829 if (appData.localLineEditing &&
2830 atoi(appData.icsPort) == TN_PORT) {
2831 TelnetRequest(TN_DONT, TN_ECHO);
2834 TelnetRequest(TN_DO, TN_ECHO);
2835 remoteEchoOption = TRUE;
2839 if (appData.debugMode)
2840 fprintf(debugFP, "%d ", option);
2841 /* Whatever this is, we don't want it. */
2842 TelnetRequest(TN_DONT, option);
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<WONT ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 if (appData.debugMode)
2852 fprintf(debugFP, "ECHO ");
2853 /* Reply only if this is a change, according
2854 to the protocol rules. */
2855 if (!remoteEchoOption) break;
2857 TelnetRequest(TN_DONT, TN_ECHO);
2858 remoteEchoOption = FALSE;
2861 if (appData.debugMode)
2862 fprintf(debugFP, "%d ", (unsigned char) option);
2863 /* Whatever this is, it must already be turned
2864 off, because we never agree to turn on
2865 anything non-default, so according to the
2866 protocol rules, we don't reply. */
2871 if (appData.debugMode)
2872 fprintf(debugFP, "\n<DO ");
2873 switch (option = (unsigned char) buf[++i]) {
2875 /* Whatever this is, we refuse to do it. */
2876 if (appData.debugMode)
2877 fprintf(debugFP, "%d ", option);
2878 TelnetRequest(TN_WONT, option);
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<DONT ");
2885 switch (option = (unsigned char) buf[++i]) {
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we are already not doing
2890 it, because we never agree to do anything
2891 non-default, so according to the protocol
2892 rules, we don't reply. */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<IAC ");
2899 /* Doubled IAC; pass it through */
2903 if (appData.debugMode)
2904 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905 /* Drop all other telnet commands on the floor */
2908 if (oldi > next_out)
2909 SendToPlayer(&buf[next_out], oldi - next_out);
2915 /* OK, this at least will *usually* work */
2916 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2920 if (loggedOn && !intfSet) {
2921 if (ics_type == ICS_ICC) {
2922 snprintf(str, MSG_SIZ,
2923 "/set-quietly interface %s\n/set-quietly style 12\n",
2925 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926 strcat(str, "/set-2 51 1\n/set seek 1\n");
2927 } else if (ics_type == ICS_CHESSNET) {
2928 snprintf(str, MSG_SIZ, "/style 12\n");
2930 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931 strcat(str, programVersion);
2932 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 strcat(str, "$iset nohighlight 1\n");
2938 strcat(str, "$iset lock 1\n$style 12\n");
2941 NotifyFrontendLogin();
2945 if (started == STARTED_COMMENT) {
2946 /* Accumulate characters in comment */
2947 parse[parse_pos++] = buf[i];
2948 if (buf[i] == '\n') {
2949 parse[parse_pos] = NULLCHAR;
2950 if(chattingPartner>=0) {
2952 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953 OutputChatMessage(chattingPartner, mess);
2954 chattingPartner = -1;
2955 next_out = i+1; // [HGM] suppress printing in ICS window
2957 if(!suppressKibitz) // [HGM] kibitz
2958 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960 int nrDigit = 0, nrAlph = 0, j;
2961 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963 parse[parse_pos] = NULLCHAR;
2964 // try to be smart: if it does not look like search info, it should go to
2965 // ICS interaction window after all, not to engine-output window.
2966 for(j=0; j<parse_pos; j++) { // count letters and digits
2967 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2969 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2971 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972 int depth=0; float score;
2973 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975 pvInfoList[forwardMostMove-1].depth = depth;
2976 pvInfoList[forwardMostMove-1].score = 100*score;
2978 OutputKibitz(suppressKibitz, parse);
2981 if(gameMode == IcsObserving) // restore original ICS messages
2982 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985 SendToPlayer(tmp, strlen(tmp));
2987 next_out = i+1; // [HGM] suppress printing in ICS window
2989 started = STARTED_NONE;
2991 /* Don't match patterns against characters in comment */
2996 if (started == STARTED_CHATTER) {
2997 if (buf[i] != '\n') {
2998 /* Don't match patterns against characters in chatter */
3002 started = STARTED_NONE;
3003 if(suppressKibitz) next_out = i+1;
3006 /* Kludge to deal with rcmd protocol */
3007 if (firstTime && looking_at(buf, &i, "\001*")) {
3008 DisplayFatalError(&buf[1], 0, 1);
3014 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017 if (appData.debugMode)
3018 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022 ics_type = ICS_FICS;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "ics_type %d\n", ics_type);
3028 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029 ics_type = ICS_CHESSNET;
3031 if (appData.debugMode)
3032 fprintf(debugFP, "ics_type %d\n", ics_type);
3037 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038 looking_at(buf, &i, "Logging you in as \"*\"") ||
3039 looking_at(buf, &i, "will be \"*\""))) {
3040 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3044 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047 DisplayIcsInteractionTitle(buf);
3048 have_set_title = TRUE;
3051 /* skip finger notes */
3052 if (started == STARTED_NONE &&
3053 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054 (buf[i] == '1' && buf[i+1] == '0')) &&
3055 buf[i+2] == ':' && buf[i+3] == ' ') {
3056 started = STARTED_CHATTER;
3062 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063 if(appData.seekGraph) {
3064 if(soughtPending && MatchSoughtLine(buf+i)) {
3065 i = strstr(buf+i, "rated") - buf;
3066 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 next_out = leftover_start = i;
3068 started = STARTED_CHATTER;
3069 suppressKibitz = TRUE;
3072 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073 && looking_at(buf, &i, "* ads displayed")) {
3074 soughtPending = FALSE;
3079 if(appData.autoRefresh) {
3080 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081 int s = (ics_type == ICS_ICC); // ICC format differs
3083 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085 looking_at(buf, &i, "*% "); // eat prompt
3086 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 next_out = i; // suppress
3091 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092 char *p = star_match[0];
3094 if(seekGraphUp) RemoveSeekAd(atoi(p));
3095 while(*p && *p++ != ' '); // next
3097 looking_at(buf, &i, "*% "); // eat prompt
3098 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 /* skip formula vars */
3106 if (started == STARTED_NONE &&
3107 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108 started = STARTED_CHATTER;
3113 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114 if (appData.autoKibitz && started == STARTED_NONE &&
3115 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3116 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3121 suppressKibitz = TRUE;
3122 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125 && (gameMode == IcsPlayingWhite)) ||
3126 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3128 started = STARTED_CHATTER; // own kibitz we simply discard
3130 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131 parse_pos = 0; parse[0] = NULLCHAR;
3132 savingComment = TRUE;
3133 suppressKibitz = gameMode != IcsObserving ? 2 :
3134 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3138 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140 && atoi(star_match[0])) {
3141 // suppress the acknowledgements of our own autoKibitz
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145 SendToPlayer(star_match[0], strlen(star_match[0]));
3146 if(looking_at(buf, &i, "*% ")) // eat prompt
3147 suppressKibitz = FALSE;
3151 } // [HGM] kibitz: end of patch
3153 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155 // [HGM] chat: intercept tells by users for which we have an open chat window
3157 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158 looking_at(buf, &i, "* whispers:") ||
3159 looking_at(buf, &i, "* kibitzes:") ||
3160 looking_at(buf, &i, "* shouts:") ||
3161 looking_at(buf, &i, "* c-shouts:") ||
3162 looking_at(buf, &i, "--> * ") ||
3163 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169 chattingPartner = -1;
3171 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174 talker[0] = '['; strcat(talker, "] ");
3175 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176 chattingPartner = p; break;
3179 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(!strcmp("kibitzes", chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 chattingPartner = p; break;
3186 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187 for(p=0; p<MAX_CHAT; p++) {
3188 if(!strcmp("whispers", chatPartner[p])) {
3189 talker[0] = '['; strcat(talker, "] ");
3190 chattingPartner = p; break;
3193 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194 if(buf[i-8] == '-' && buf[i-3] == 't')
3195 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196 if(!strcmp("c-shouts", chatPartner[p])) {
3197 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198 chattingPartner = p; break;
3201 if(chattingPartner < 0)
3202 for(p=0; p<MAX_CHAT; p++) {
3203 if(!strcmp("shouts", chatPartner[p])) {
3204 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207 chattingPartner = p; break;
3211 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213 talker[0] = 0; Colorize(ColorTell, FALSE);
3214 chattingPartner = p; break;
3216 if(chattingPartner<0) i = oldi; else {
3217 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 started = STARTED_COMMENT;
3221 parse_pos = 0; parse[0] = NULLCHAR;
3222 savingComment = 3 + chattingPartner; // counts as TRUE
3223 suppressKibitz = TRUE;
3226 } // [HGM] chat: end of patch
3229 if (appData.zippyTalk || appData.zippyPlay) {
3230 /* [DM] Backup address for color zippy lines */
3232 if (loggedOn == TRUE)
3233 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 } // [DM] 'else { ' deleted
3238 /* Regular tells and says */
3239 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240 looking_at(buf, &i, "* (your partner) tells you: ") ||
3241 looking_at(buf, &i, "* says: ") ||
3242 /* Don't color "message" or "messages" output */
3243 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244 looking_at(buf, &i, "*. * at *:*: ") ||
3245 looking_at(buf, &i, "--* (*:*): ") ||
3246 /* Message notifications (same color as tells) */
3247 looking_at(buf, &i, "* has left a message ") ||
3248 looking_at(buf, &i, "* just sent you a message:\n") ||
3249 /* Whispers and kibitzes */
3250 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251 looking_at(buf, &i, "* kibitzes: ") ||
3253 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255 if (tkind == 1 && strchr(star_match[0], ':')) {
3256 /* Avoid "tells you:" spoofs in channels */
3259 if (star_match[0][0] == NULLCHAR ||
3260 strchr(star_match[0], ' ') ||
3261 (tkind == 3 && strchr(star_match[1], ' '))) {
3262 /* Reject bogus matches */
3265 if (appData.colorize) {
3266 if (oldi > next_out) {
3267 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorTell, FALSE);
3273 curColor = ColorTell;
3276 Colorize(ColorKibitz, FALSE);
3277 curColor = ColorKibitz;
3280 p = strrchr(star_match[1], '(');
3287 Colorize(ColorChannel1, FALSE);
3288 curColor = ColorChannel1;
3290 Colorize(ColorChannel, FALSE);
3291 curColor = ColorChannel;
3295 curColor = ColorNormal;
3299 if (started == STARTED_NONE && appData.autoComment &&
3300 (gameMode == IcsObserving ||
3301 gameMode == IcsPlayingWhite ||
3302 gameMode == IcsPlayingBlack)) {
3303 parse_pos = i - oldi;
3304 memcpy(parse, &buf[oldi], parse_pos);
3305 parse[parse_pos] = NULLCHAR;
3306 started = STARTED_COMMENT;
3307 savingComment = TRUE;
3309 started = STARTED_CHATTER;
3310 savingComment = FALSE;
3317 if (looking_at(buf, &i, "* s-shouts: ") ||
3318 looking_at(buf, &i, "* c-shouts: ")) {
3319 if (appData.colorize) {
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorSShout, FALSE);
3325 curColor = ColorSShout;
3328 started = STARTED_CHATTER;
3332 if (looking_at(buf, &i, "--->")) {
3337 if (looking_at(buf, &i, "* shouts: ") ||
3338 looking_at(buf, &i, "--> ")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorShout, FALSE);
3345 curColor = ColorShout;
3348 started = STARTED_CHATTER;
3352 if (looking_at( buf, &i, "Challenge:")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorChallenge, FALSE);
3359 curColor = ColorChallenge;
3365 if (looking_at(buf, &i, "* offers you") ||
3366 looking_at(buf, &i, "* offers to be") ||
3367 looking_at(buf, &i, "* would like to") ||
3368 looking_at(buf, &i, "* requests to") ||
3369 looking_at(buf, &i, "Your opponent offers") ||
3370 looking_at(buf, &i, "Your opponent requests")) {
3372 if (appData.colorize) {
3373 if (oldi > next_out) {
3374 SendToPlayer(&buf[next_out], oldi - next_out);
3377 Colorize(ColorRequest, FALSE);
3378 curColor = ColorRequest;
3383 if (looking_at(buf, &i, "* (*) seeking")) {
3384 if (appData.colorize) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 Colorize(ColorSeek, FALSE);
3390 curColor = ColorSeek;
3395 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397 if (looking_at(buf, &i, "\\ ")) {
3398 if (prevColor != ColorNormal) {
3399 if (oldi > next_out) {
3400 SendToPlayer(&buf[next_out], oldi - next_out);
3403 Colorize(prevColor, TRUE);
3404 curColor = prevColor;
3406 if (savingComment) {
3407 parse_pos = i - oldi;
3408 memcpy(parse, &buf[oldi], parse_pos);
3409 parse[parse_pos] = NULLCHAR;
3410 started = STARTED_COMMENT;
3411 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412 chattingPartner = savingComment - 3; // kludge to remember the box
3414 started = STARTED_CHATTER;
3419 if (looking_at(buf, &i, "Black Strength :") ||
3420 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421 looking_at(buf, &i, "<10>") ||
3422 looking_at(buf, &i, "#@#")) {
3423 /* Wrong board style */
3425 SendToICS(ics_prefix);
3426 SendToICS("set style 12\n");
3427 SendToICS(ics_prefix);
3428 SendToICS("refresh\n");
3432 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434 have_sent_ICS_logon = 1;
3438 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439 (looking_at(buf, &i, "\n<12> ") ||
3440 looking_at(buf, &i, "<12> "))) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 started = STARTED_BOARD;
3451 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452 looking_at(buf, &i, "<b1> ")) {
3453 if (oldi > next_out) {
3454 SendToPlayer(&buf[next_out], oldi - next_out);
3457 started = STARTED_HOLDINGS;
3462 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464 /* Header for a move list -- first line */
3466 switch (ics_getting_history) {
3470 case BeginningOfGame:
3471 /* User typed "moves" or "oldmoves" while we
3472 were idle. Pretend we asked for these
3473 moves and soak them up so user can step
3474 through them and/or save them.
3477 gameMode = IcsObserving;
3480 ics_getting_history = H_GOT_UNREQ_HEADER;
3482 case EditGame: /*?*/
3483 case EditPosition: /*?*/
3484 /* Should above feature work in these modes too? */
3485 /* For now it doesn't */
3486 ics_getting_history = H_GOT_UNWANTED_HEADER;
3489 ics_getting_history = H_GOT_UNWANTED_HEADER;
3494 /* Is this the right one? */
3495 if (gameInfo.white && gameInfo.black &&
3496 strcmp(gameInfo.white, star_match[0]) == 0 &&
3497 strcmp(gameInfo.black, star_match[2]) == 0) {
3499 ics_getting_history = H_GOT_REQ_HEADER;
3502 case H_GOT_REQ_HEADER:
3503 case H_GOT_UNREQ_HEADER:
3504 case H_GOT_UNWANTED_HEADER:
3505 case H_GETTING_MOVES:
3506 /* Should not happen */
3507 DisplayError(_("Error gathering move list: two headers"), 0);
3508 ics_getting_history = H_FALSE;
3512 /* Save player ratings into gameInfo if needed */
3513 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515 (gameInfo.whiteRating == -1 ||
3516 gameInfo.blackRating == -1)) {
3518 gameInfo.whiteRating = string_to_rating(star_match[1]);
3519 gameInfo.blackRating = string_to_rating(star_match[3]);
3520 if (appData.debugMode)
3521 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522 gameInfo.whiteRating, gameInfo.blackRating);
3527 if (looking_at(buf, &i,
3528 "* * match, initial time: * minute*, increment: * second")) {
3529 /* Header for a move list -- second line */
3530 /* Initial board will follow if this is a wild game */
3531 if (gameInfo.event != NULL) free(gameInfo.event);
3532 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533 gameInfo.event = StrSave(str);
3534 /* [HGM] we switched variant. Translate boards if needed. */
3535 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3539 if (looking_at(buf, &i, "Move ")) {
3540 /* Beginning of a move list */
3541 switch (ics_getting_history) {
3543 /* Normally should not happen */
3544 /* Maybe user hit reset while we were parsing */
3547 /* Happens if we are ignoring a move list that is not
3548 * the one we just requested. Common if the user
3549 * tries to observe two games without turning off
3552 case H_GETTING_MOVES:
3553 /* Should not happen */
3554 DisplayError(_("Error gathering move list: nested"), 0);
3555 ics_getting_history = H_FALSE;
3557 case H_GOT_REQ_HEADER:
3558 ics_getting_history = H_GETTING_MOVES;
3559 started = STARTED_MOVES;
3561 if (oldi > next_out) {
3562 SendToPlayer(&buf[next_out], oldi - next_out);
3565 case H_GOT_UNREQ_HEADER:
3566 ics_getting_history = H_GETTING_MOVES;
3567 started = STARTED_MOVES_NOHIDE;
3570 case H_GOT_UNWANTED_HEADER:
3571 ics_getting_history = H_FALSE;
3577 if (looking_at(buf, &i, "% ") ||
3578 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581 soughtPending = FALSE;
3585 if(suppressKibitz) next_out = i;
3586 savingComment = FALSE;
3590 case STARTED_MOVES_NOHIDE:
3591 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592 parse[parse_pos + i - oldi] = NULLCHAR;
3593 ParseGameHistory(parse);
3595 if (appData.zippyPlay && first.initDone) {
3596 FeedMovesToProgram(&first, forwardMostMove);
3597 if (gameMode == IcsPlayingWhite) {
3598 if (WhiteOnMove(forwardMostMove)) {
3599 if (first.sendTime) {
3600 if (first.useColors) {
3601 SendToProgram("black\n", &first);
3603 SendTimeRemaining(&first, TRUE);
3605 if (first.useColors) {
3606 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609 first.maybeThinking = TRUE;
3611 if (first.usePlayother) {
3612 if (first.sendTime) {
3613 SendTimeRemaining(&first, TRUE);
3615 SendToProgram("playother\n", &first);
3621 } else if (gameMode == IcsPlayingBlack) {
3622 if (!WhiteOnMove(forwardMostMove)) {
3623 if (first.sendTime) {
3624 if (first.useColors) {
3625 SendToProgram("white\n", &first);
3627 SendTimeRemaining(&first, FALSE);
3629 if (first.useColors) {
3630 SendToProgram("black\n", &first);
3632 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633 first.maybeThinking = TRUE;
3635 if (first.usePlayother) {
3636 if (first.sendTime) {
3637 SendTimeRemaining(&first, FALSE);
3639 SendToProgram("playother\n", &first);
3648 if (gameMode == IcsObserving && ics_gamenum == -1) {
3649 /* Moves came from oldmoves or moves command
3650 while we weren't doing anything else.
3652 currentMove = forwardMostMove;
3653 ClearHighlights();/*!!could figure this out*/
3654 flipView = appData.flipView;
3655 DrawPosition(TRUE, boards[currentMove]);
3656 DisplayBothClocks();
3657 snprintf(str, MSG_SIZ, "%s %s %s",
3658 gameInfo.white, _("vs."), gameInfo.black);
3662 /* Moves were history of an active game */
3663 if (gameInfo.resultDetails != NULL) {
3664 free(gameInfo.resultDetails);
3665 gameInfo.resultDetails = NULL;
3668 HistorySet(parseList, backwardMostMove,
3669 forwardMostMove, currentMove-1);
3670 DisplayMove(currentMove - 1);
3671 if (started == STARTED_MOVES) next_out = i;
3672 started = STARTED_NONE;
3673 ics_getting_history = H_FALSE;
3676 case STARTED_OBSERVE:
3677 started = STARTED_NONE;
3678 SendToICS(ics_prefix);
3679 SendToICS("refresh\n");
3685 if(bookHit) { // [HGM] book: simulate book reply
3686 static char bookMove[MSG_SIZ]; // a bit generous?
3688 programStats.nodes = programStats.depth = programStats.time =
3689 programStats.score = programStats.got_only_move = 0;
3690 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693 strcat(bookMove, bookHit);
3694 HandleMachineMove(bookMove, &first);
3699 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700 started == STARTED_HOLDINGS ||
3701 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702 /* Accumulate characters in move list or board */
3703 parse[parse_pos++] = buf[i];
3706 /* Start of game messages. Mostly we detect start of game
3707 when the first board image arrives. On some versions
3708 of the ICS, though, we need to do a "refresh" after starting
3709 to observe in order to get the current board right away. */
3710 if (looking_at(buf, &i, "Adding game * to observation list")) {
3711 started = STARTED_OBSERVE;
3715 /* Handle auto-observe */
3716 if (appData.autoObserve &&
3717 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720 /* Choose the player that was highlighted, if any. */
3721 if (star_match[0][0] == '\033' ||
3722 star_match[1][0] != '\033') {
3723 player = star_match[0];
3725 player = star_match[2];
3727 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728 ics_prefix, StripHighlightAndTitle(player));
3731 /* Save ratings from notify string */
3732 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733 player1Rating = string_to_rating(star_match[1]);
3734 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735 player2Rating = string_to_rating(star_match[3]);
3737 if (appData.debugMode)
3739 "Ratings from 'Game notification:' %s %d, %s %d\n",
3740 player1Name, player1Rating,
3741 player2Name, player2Rating);
3746 /* Deal with automatic examine mode after a game,
3747 and with IcsObserving -> IcsExamining transition */
3748 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749 looking_at(buf, &i, "has made you an examiner of game *")) {
3751 int gamenum = atoi(star_match[0]);
3752 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753 gamenum == ics_gamenum) {
3754 /* We were already playing or observing this game;
3755 no need to refetch history */
3756 gameMode = IcsExamining;
3758 pauseExamForwardMostMove = forwardMostMove;
3759 } else if (currentMove < forwardMostMove) {
3760 ForwardInner(forwardMostMove);
3763 /* I don't think this case really can happen */
3764 SendToICS(ics_prefix);
3765 SendToICS("refresh\n");
3770 /* Error messages */
3771 // if (ics_user_moved) {
3772 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773 if (looking_at(buf, &i, "Illegal move") ||
3774 looking_at(buf, &i, "Not a legal move") ||
3775 looking_at(buf, &i, "Your king is in check") ||
3776 looking_at(buf, &i, "It isn't your turn") ||
3777 looking_at(buf, &i, "It is not your move")) {
3779 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780 currentMove = forwardMostMove-1;
3781 DisplayMove(currentMove - 1); /* before DMError */
3782 DrawPosition(FALSE, boards[currentMove]);
3783 SwitchClocks(forwardMostMove-1); // [HGM] race
3784 DisplayBothClocks();
3786 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3792 if (looking_at(buf, &i, "still have time") ||
3793 looking_at(buf, &i, "not out of time") ||
3794 looking_at(buf, &i, "either player is out of time") ||
3795 looking_at(buf, &i, "has timeseal; checking")) {
3796 /* We must have called his flag a little too soon */
3797 whiteFlag = blackFlag = FALSE;
3801 if (looking_at(buf, &i, "added * seconds to") ||
3802 looking_at(buf, &i, "seconds were added to")) {
3803 /* Update the clocks */
3804 SendToICS(ics_prefix);
3805 SendToICS("refresh\n");
3809 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810 ics_clock_paused = TRUE;
3815 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816 ics_clock_paused = FALSE;
3821 /* Grab player ratings from the Creating: message.
3822 Note we have to check for the special case when
3823 the ICS inserts things like [white] or [black]. */
3824 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827 0 player 1 name (not necessarily white)
3829 2 empty, white, or black (IGNORED)
3830 3 player 2 name (not necessarily black)
3833 The names/ratings are sorted out when the game
3834 actually starts (below).
3836 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837 player1Rating = string_to_rating(star_match[1]);
3838 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839 player2Rating = string_to_rating(star_match[4]);
3841 if (appData.debugMode)
3843 "Ratings from 'Creating:' %s %d, %s %d\n",
3844 player1Name, player1Rating,
3845 player2Name, player2Rating);
3850 /* Improved generic start/end-of-game messages */
3851 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853 /* If tkind == 0: */
3854 /* star_match[0] is the game number */
3855 /* [1] is the white player's name */
3856 /* [2] is the black player's name */
3857 /* For end-of-game: */
3858 /* [3] is the reason for the game end */
3859 /* [4] is a PGN end game-token, preceded by " " */
3860 /* For start-of-game: */
3861 /* [3] begins with "Creating" or "Continuing" */
3862 /* [4] is " *" or empty (don't care). */
3863 int gamenum = atoi(star_match[0]);
3864 char *whitename, *blackname, *why, *endtoken;
3865 ChessMove endtype = EndOfFile;
3868 whitename = star_match[1];
3869 blackname = star_match[2];
3870 why = star_match[3];
3871 endtoken = star_match[4];
3873 whitename = star_match[1];
3874 blackname = star_match[3];
3875 why = star_match[5];
3876 endtoken = star_match[6];
3879 /* Game start messages */
3880 if (strncmp(why, "Creating ", 9) == 0 ||
3881 strncmp(why, "Continuing ", 11) == 0) {
3882 gs_gamenum = gamenum;
3883 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 if (appData.zippyPlay) {
3888 ZippyGameStart(whitename, blackname);
3891 partnerBoardValid = FALSE; // [HGM] bughouse
3895 /* Game end messages */
3896 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897 ics_gamenum != gamenum) {
3900 while (endtoken[0] == ' ') endtoken++;
3901 switch (endtoken[0]) {
3904 endtype = GameUnfinished;
3907 endtype = BlackWins;
3910 if (endtoken[1] == '/')
3911 endtype = GameIsDrawn;
3913 endtype = WhiteWins;
3916 GameEnds(endtype, why, GE_ICS);
3918 if (appData.zippyPlay && first.initDone) {
3919 ZippyGameEnd(endtype, why);
3920 if (first.pr == NoProc) {
3921 /* Start the next process early so that we'll
3922 be ready for the next challenge */
3923 StartChessProgram(&first);
3925 /* Send "new" early, in case this command takes
3926 a long time to finish, so that we'll be ready
3927 for the next challenge. */
3928 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3932 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3936 if (looking_at(buf, &i, "Removing game * from observation") ||
3937 looking_at(buf, &i, "no longer observing game *") ||
3938 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939 if (gameMode == IcsObserving &&
3940 atoi(star_match[0]) == ics_gamenum)
3942 /* icsEngineAnalyze */
3943 if (appData.icsEngineAnalyze) {
3950 ics_user_moved = FALSE;
3955 if (looking_at(buf, &i, "no longer examining game *")) {
3956 if (gameMode == IcsExamining &&
3957 atoi(star_match[0]) == ics_gamenum)
3961 ics_user_moved = FALSE;
3966 /* Advance leftover_start past any newlines we find,
3967 so only partial lines can get reparsed */
3968 if (looking_at(buf, &i, "\n")) {
3969 prevColor = curColor;
3970 if (curColor != ColorNormal) {
3971 if (oldi > next_out) {
3972 SendToPlayer(&buf[next_out], oldi - next_out);
3975 Colorize(ColorNormal, FALSE);
3976 curColor = ColorNormal;
3978 if (started == STARTED_BOARD) {
3979 started = STARTED_NONE;
3980 parse[parse_pos] = NULLCHAR;
3981 ParseBoard12(parse);
3984 /* Send premove here */
3985 if (appData.premove) {
3987 if (currentMove == 0 &&
3988 gameMode == IcsPlayingWhite &&
3989 appData.premoveWhite) {
3990 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991 if (appData.debugMode)
3992 fprintf(debugFP, "Sending premove:\n");
3994 } else if (currentMove == 1 &&
3995 gameMode == IcsPlayingBlack &&
3996 appData.premoveBlack) {
3997 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998 if (appData.debugMode)
3999 fprintf(debugFP, "Sending premove:\n");
4001 } else if (gotPremove) {
4003 ClearPremoveHighlights();
4004 if (appData.debugMode)
4005 fprintf(debugFP, "Sending premove:\n");
4006 UserMoveEvent(premoveFromX, premoveFromY,
4007 premoveToX, premoveToY,
4012 /* Usually suppress following prompt */
4013 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015 if (looking_at(buf, &i, "*% ")) {
4016 savingComment = FALSE;
4021 } else if (started == STARTED_HOLDINGS) {
4023 char new_piece[MSG_SIZ];
4024 started = STARTED_NONE;
4025 parse[parse_pos] = NULLCHAR;
4026 if (appData.debugMode)
4027 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028 parse, currentMove);
4029 if (sscanf(parse, " game %d", &gamenum) == 1) {
4030 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031 if (gameInfo.variant == VariantNormal) {
4032 /* [HGM] We seem to switch variant during a game!
4033 * Presumably no holdings were displayed, so we have
4034 * to move the position two files to the right to
4035 * create room for them!
4037 VariantClass newVariant;
4038 switch(gameInfo.boardWidth) { // base guess on board width
4039 case 9: newVariant = VariantShogi; break;
4040 case 10: newVariant = VariantGreat; break;
4041 default: newVariant = VariantCrazyhouse; break;
4043 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044 /* Get a move list just to see the header, which
4045 will tell us whether this is really bug or zh */
4046 if (ics_getting_history == H_FALSE) {
4047 ics_getting_history = H_REQUESTED;
4048 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4052 new_piece[0] = NULLCHAR;
4053 sscanf(parse, "game %d white [%s black [%s <- %s",
4054 &gamenum, white_holding, black_holding,
4056 white_holding[strlen(white_holding)-1] = NULLCHAR;
4057 black_holding[strlen(black_holding)-1] = NULLCHAR;
4058 /* [HGM] copy holdings to board holdings area */
4059 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 if (appData.zippyPlay && first.initDone) {
4064 ZippyHoldings(white_holding, black_holding,
4068 if (tinyLayout || smallLayout) {
4069 char wh[16], bh[16];
4070 PackHolding(wh, white_holding);
4071 PackHolding(bh, black_holding);
4072 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073 gameInfo.white, gameInfo.black);
4075 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076 gameInfo.white, white_holding, _("vs."),
4077 gameInfo.black, black_holding);
4079 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080 DrawPosition(FALSE, boards[currentMove]);
4082 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083 sscanf(parse, "game %d white [%s black [%s <- %s",
4084 &gamenum, white_holding, black_holding,
4086 white_holding[strlen(white_holding)-1] = NULLCHAR;
4087 black_holding[strlen(black_holding)-1] = NULLCHAR;
4088 /* [HGM] copy holdings to partner-board holdings area */
4089 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4096 /* Suppress following prompt */
4097 if (looking_at(buf, &i, "*% ")) {
4098 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099 savingComment = FALSE;
4107 i++; /* skip unparsed character and loop back */
4110 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 // SendToPlayer(&buf[next_out], i - next_out);
4113 started != STARTED_HOLDINGS && leftover_start > next_out) {
4114 SendToPlayer(&buf[next_out], leftover_start - next_out);
4118 leftover_len = buf_len - leftover_start;
4119 /* if buffer ends with something we couldn't parse,
4120 reparse it after appending the next read */
4122 } else if (count == 0) {
4123 RemoveInputSource(isr);
4124 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126 DisplayFatalError(_("Error reading from ICS"), error, 1);
4131 /* Board style 12 looks like this:
4133 <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
4135 * The "<12> " is stripped before it gets to this routine. The two
4136 * trailing 0's (flip state and clock ticking) are later addition, and
4137 * some chess servers may not have them, or may have only the first.
4138 * Additional trailing fields may be added in the future.
4141 #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"
4143 #define RELATION_OBSERVING_PLAYED 0
4144 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE 1
4146 #define RELATION_PLAYING_NOTMYMOVE -1
4147 #define RELATION_EXAMINING 2
4148 #define RELATION_ISOLATED_BOARD -3
4149 #define RELATION_STARTING_POSITION -4 /* FICS only */
4152 ParseBoard12 (char *string)
4154 GameMode newGameMode;
4155 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158 char to_play, board_chars[200];
4159 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160 char black[32], white[32];
4162 int prevMove = currentMove;
4165 int fromX, fromY, toX, toY;
4167 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168 char *bookHit = NULL; // [HGM] book
4169 Boolean weird = FALSE, reqFlag = FALSE;
4171 fromX = fromY = toX = toY = -1;
4175 if (appData.debugMode)
4176 fprintf(debugFP, _("Parsing board: %s\n"), string);
4178 move_str[0] = NULLCHAR;
4179 elapsed_time[0] = NULLCHAR;
4180 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183 if(string[i] == ' ') { ranks++; files = 0; }
4185 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4188 for(j = 0; j <i; j++) board_chars[j] = string[j];
4189 board_chars[i] = '\0';
4192 n = sscanf(string, PATTERN, &to_play, &double_push,
4193 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194 &gamenum, white, black, &relation, &basetime, &increment,
4195 &white_stren, &black_stren, &white_time, &black_time,
4196 &moveNum, str, elapsed_time, move_str, &ics_flip,
4200 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201 DisplayError(str, 0);
4205 /* Convert the move number to internal form */
4206 moveNum = (moveNum - 1) * 2;
4207 if (to_play == 'B') moveNum++;
4208 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4215 case RELATION_OBSERVING_PLAYED:
4216 case RELATION_OBSERVING_STATIC:
4217 if (gamenum == -1) {
4218 /* Old ICC buglet */
4219 relation = RELATION_OBSERVING_STATIC;
4221 newGameMode = IcsObserving;
4223 case RELATION_PLAYING_MYMOVE:
4224 case RELATION_PLAYING_NOTMYMOVE:
4226 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227 IcsPlayingWhite : IcsPlayingBlack;
4228 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230 case RELATION_EXAMINING:
4231 newGameMode = IcsExamining;
4233 case RELATION_ISOLATED_BOARD:
4235 /* Just display this board. If user was doing something else,
4236 we will forget about it until the next board comes. */
4237 newGameMode = IcsIdle;
4239 case RELATION_STARTING_POSITION:
4240 newGameMode = gameMode;
4244 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249 for (k = 0; k < ranks; k++) {
4250 for (j = 0; j < files; j++)
4251 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4252 if(gameInfo.holdingsWidth > 1) {
4253 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4254 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4257 CopyBoard(partnerBoard, board);
4258 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4259 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4260 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4261 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4262 if(toSqr = strchr(str, '-')) {
4263 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4264 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4265 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4266 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4267 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4268 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4270 DisplayWhiteClock(white_time*fac, to_play == 'W');
4271 DisplayBlackClock(black_time*fac, to_play != 'W');
4272 activePartner = to_play;
4273 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4274 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4275 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4276 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4277 DisplayMessage(partnerStatus, "");
4278 partnerBoardValid = TRUE;
4282 /* Modify behavior for initial board display on move listing
4285 switch (ics_getting_history) {
4289 case H_GOT_REQ_HEADER:
4290 case H_GOT_UNREQ_HEADER:
4291 /* This is the initial position of the current game */
4292 gamenum = ics_gamenum;
4293 moveNum = 0; /* old ICS bug workaround */
4294 if (to_play == 'B') {
4295 startedFromSetupPosition = TRUE;
4296 blackPlaysFirst = TRUE;
4298 if (forwardMostMove == 0) forwardMostMove = 1;
4299 if (backwardMostMove == 0) backwardMostMove = 1;
4300 if (currentMove == 0) currentMove = 1;
4302 newGameMode = gameMode;
4303 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4305 case H_GOT_UNWANTED_HEADER:
4306 /* This is an initial board that we don't want */
4308 case H_GETTING_MOVES:
4309 /* Should not happen */
4310 DisplayError(_("Error gathering move list: extra board"), 0);
4311 ics_getting_history = H_FALSE;
4315 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4316 weird && (int)gameInfo.variant < (int)VariantShogi) {
4317 /* [HGM] We seem to have switched variant unexpectedly
4318 * Try to guess new variant from board size
4320 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4321 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4322 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4323 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4324 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4325 if(!weird) newVariant = VariantNormal;
4326 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4327 /* Get a move list just to see the header, which
4328 will tell us whether this is really bug or zh */
4329 if (ics_getting_history == H_FALSE) {
4330 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4331 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4336 /* Take action if this is the first board of a new game, or of a
4337 different game than is currently being displayed. */
4338 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4339 relation == RELATION_ISOLATED_BOARD) {
4341 /* Forget the old game and get the history (if any) of the new one */
4342 if (gameMode != BeginningOfGame) {
4346 if (appData.autoRaiseBoard) BoardToTop();
4348 if (gamenum == -1) {
4349 newGameMode = IcsIdle;
4350 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4351 appData.getMoveList && !reqFlag) {
4352 /* Need to get game history */
4353 ics_getting_history = H_REQUESTED;
4354 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4358 /* Initially flip the board to have black on the bottom if playing
4359 black or if the ICS flip flag is set, but let the user change
4360 it with the Flip View button. */
4361 flipView = appData.autoFlipView ?
4362 (newGameMode == IcsPlayingBlack) || ics_flip :
4365 /* Done with values from previous mode; copy in new ones */
4366 gameMode = newGameMode;
4368 ics_gamenum = gamenum;
4369 if (gamenum == gs_gamenum) {
4370 int klen = strlen(gs_kind);
4371 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4372 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4373 gameInfo.event = StrSave(str);
4375 gameInfo.event = StrSave("ICS game");
4377 gameInfo.site = StrSave(appData.icsHost);
4378 gameInfo.date = PGNDate();
4379 gameInfo.round = StrSave("-");
4380 gameInfo.white = StrSave(white);
4381 gameInfo.black = StrSave(black);
4382 timeControl = basetime * 60 * 1000;
4384 timeIncrement = increment * 1000;
4385 movesPerSession = 0;
4386 gameInfo.timeControl = TimeControlTagValue();
4387 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4388 if (appData.debugMode) {
4389 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4390 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4391 setbuf(debugFP, NULL);
4394 gameInfo.outOfBook = NULL;
4396 /* Do we have the ratings? */
4397 if (strcmp(player1Name, white) == 0 &&
4398 strcmp(player2Name, black) == 0) {
4399 if (appData.debugMode)
4400 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4401 player1Rating, player2Rating);
4402 gameInfo.whiteRating = player1Rating;
4403 gameInfo.blackRating = player2Rating;
4404 } else if (strcmp(player2Name, white) == 0 &&
4405 strcmp(player1Name, black) == 0) {
4406 if (appData.debugMode)
4407 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4408 player2Rating, player1Rating);
4409 gameInfo.whiteRating = player2Rating;
4410 gameInfo.blackRating = player1Rating;
4412 player1Name[0] = player2Name[0] = NULLCHAR;
4414 /* Silence shouts if requested */
4415 if (appData.quietPlay &&
4416 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4417 SendToICS(ics_prefix);
4418 SendToICS("set shout 0\n");
4422 /* Deal with midgame name changes */
4424 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4425 if (gameInfo.white) free(gameInfo.white);
4426 gameInfo.white = StrSave(white);
4428 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4429 if (gameInfo.black) free(gameInfo.black);
4430 gameInfo.black = StrSave(black);
4434 /* Throw away game result if anything actually changes in examine mode */
4435 if (gameMode == IcsExamining && !newGame) {
4436 gameInfo.result = GameUnfinished;
4437 if (gameInfo.resultDetails != NULL) {
4438 free(gameInfo.resultDetails);
4439 gameInfo.resultDetails = NULL;
4443 /* In pausing && IcsExamining mode, we ignore boards coming
4444 in if they are in a different variation than we are. */
4445 if (pauseExamInvalid) return;
4446 if (pausing && gameMode == IcsExamining) {
4447 if (moveNum <= pauseExamForwardMostMove) {
4448 pauseExamInvalid = TRUE;
4449 forwardMostMove = pauseExamForwardMostMove;
4454 if (appData.debugMode) {
4455 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4457 /* Parse the board */
4458 for (k = 0; k < ranks; k++) {
4459 for (j = 0; j < files; j++)
4460 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4461 if(gameInfo.holdingsWidth > 1) {
4462 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4463 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4466 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4467 board[5][BOARD_RGHT+1] = WhiteAngel;
4468 board[6][BOARD_RGHT+1] = WhiteMarshall;
4469 board[1][0] = BlackMarshall;
4470 board[2][0] = BlackAngel;
4471 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4473 CopyBoard(boards[moveNum], board);
4474 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4476 startedFromSetupPosition =
4477 !CompareBoards(board, initialPosition);
4478 if(startedFromSetupPosition)
4479 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4482 /* [HGM] Set castling rights. Take the outermost Rooks,
4483 to make it also work for FRC opening positions. Note that board12
4484 is really defective for later FRC positions, as it has no way to
4485 indicate which Rook can castle if they are on the same side of King.
4486 For the initial position we grant rights to the outermost Rooks,
4487 and remember thos rights, and we then copy them on positions
4488 later in an FRC game. This means WB might not recognize castlings with
4489 Rooks that have moved back to their original position as illegal,
4490 but in ICS mode that is not its job anyway.
4492 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4493 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4495 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496 if(board[0][i] == WhiteRook) j = i;
4497 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499 if(board[0][i] == WhiteRook) j = i;
4500 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4502 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4505 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4508 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4509 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4510 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4511 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4512 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4513 if(board[BOARD_HEIGHT-1][k] == bKing)
4514 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4515 if(gameInfo.variant == VariantTwoKings) {
4516 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4517 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4518 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4521 r = boards[moveNum][CASTLING][0] = initialRights[0];
4522 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4523 r = boards[moveNum][CASTLING][1] = initialRights[1];
4524 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4525 r = boards[moveNum][CASTLING][3] = initialRights[3];
4526 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4527 r = boards[moveNum][CASTLING][4] = initialRights[4];
4528 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4529 /* wildcastle kludge: always assume King has rights */
4530 r = boards[moveNum][CASTLING][2] = initialRights[2];
4531 r = boards[moveNum][CASTLING][5] = initialRights[5];
4533 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4534 boards[moveNum][EP_STATUS] = EP_NONE;
4535 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4536 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4537 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4540 if (ics_getting_history == H_GOT_REQ_HEADER ||
4541 ics_getting_history == H_GOT_UNREQ_HEADER) {
4542 /* This was an initial position from a move list, not
4543 the current position */
4547 /* Update currentMove and known move number limits */
4548 newMove = newGame || moveNum > forwardMostMove;
4551 forwardMostMove = backwardMostMove = currentMove = moveNum;
4552 if (gameMode == IcsExamining && moveNum == 0) {
4553 /* Workaround for ICS limitation: we are not told the wild
4554 type when starting to examine a game. But if we ask for
4555 the move list, the move list header will tell us */
4556 ics_getting_history = H_REQUESTED;
4557 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4560 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4561 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4563 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4564 /* [HGM] applied this also to an engine that is silently watching */
4565 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4566 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4567 gameInfo.variant == currentlyInitializedVariant) {
4568 takeback = forwardMostMove - moveNum;
4569 for (i = 0; i < takeback; i++) {
4570 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4571 SendToProgram("undo\n", &first);
4576 forwardMostMove = moveNum;
4577 if (!pausing || currentMove > forwardMostMove)
4578 currentMove = forwardMostMove;
4580 /* New part of history that is not contiguous with old part */
4581 if (pausing && gameMode == IcsExamining) {
4582 pauseExamInvalid = TRUE;
4583 forwardMostMove = pauseExamForwardMostMove;
4586 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4588 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4589 // [HGM] when we will receive the move list we now request, it will be
4590 // fed to the engine from the first move on. So if the engine is not
4591 // in the initial position now, bring it there.
4592 InitChessProgram(&first, 0);
4595 ics_getting_history = H_REQUESTED;
4596 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4599 forwardMostMove = backwardMostMove = currentMove = moveNum;
4602 /* Update the clocks */
4603 if (strchr(elapsed_time, '.')) {
4605 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4606 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4608 /* Time is in seconds */
4609 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4610 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4615 if (appData.zippyPlay && newGame &&
4616 gameMode != IcsObserving && gameMode != IcsIdle &&
4617 gameMode != IcsExamining)
4618 ZippyFirstBoard(moveNum, basetime, increment);
4621 /* Put the move on the move list, first converting
4622 to canonical algebraic form. */
4624 if (appData.debugMode) {
4625 if (appData.debugMode) { int f = forwardMostMove;
4626 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4627 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4628 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4630 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4631 fprintf(debugFP, "moveNum = %d\n", moveNum);
4632 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4633 setbuf(debugFP, NULL);
4635 if (moveNum <= backwardMostMove) {
4636 /* We don't know what the board looked like before
4638 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4639 strcat(parseList[moveNum - 1], " ");
4640 strcat(parseList[moveNum - 1], elapsed_time);
4641 moveList[moveNum - 1][0] = NULLCHAR;
4642 } else if (strcmp(move_str, "none") == 0) {
4643 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4644 /* Again, we don't know what the board looked like;
4645 this is really the start of the game. */
4646 parseList[moveNum - 1][0] = NULLCHAR;
4647 moveList[moveNum - 1][0] = NULLCHAR;
4648 backwardMostMove = moveNum;
4649 startedFromSetupPosition = TRUE;
4650 fromX = fromY = toX = toY = -1;
4652 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4653 // So we parse the long-algebraic move string in stead of the SAN move
4654 int valid; char buf[MSG_SIZ], *prom;
4656 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4657 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4658 // str looks something like "Q/a1-a2"; kill the slash
4660 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4661 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4662 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4663 strcat(buf, prom); // long move lacks promo specification!
4664 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4665 if(appData.debugMode)
4666 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4667 safeStrCpy(move_str, buf, MSG_SIZ);
4669 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4670 &fromX, &fromY, &toX, &toY, &promoChar)
4671 || ParseOneMove(buf, moveNum - 1, &moveType,
4672 &fromX, &fromY, &toX, &toY, &promoChar);
4673 // end of long SAN patch
4675 (void) CoordsToAlgebraic(boards[moveNum - 1],
4676 PosFlags(moveNum - 1),
4677 fromY, fromX, toY, toX, promoChar,
4678 parseList[moveNum-1]);
4679 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4685 if(gameInfo.variant != VariantShogi)
4686 strcat(parseList[moveNum - 1], "+");
4689 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4690 strcat(parseList[moveNum - 1], "#");
4693 strcat(parseList[moveNum - 1], " ");
4694 strcat(parseList[moveNum - 1], elapsed_time);
4695 /* currentMoveString is set as a side-effect of ParseOneMove */
4696 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4697 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4698 strcat(moveList[moveNum - 1], "\n");
4700 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4701 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4702 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4703 ChessSquare old, new = boards[moveNum][k][j];
4704 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4705 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4706 if(old == new) continue;
4707 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4708 else if(new == WhiteWazir || new == BlackWazir) {
4709 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4710 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4711 else boards[moveNum][k][j] = old; // preserve type of Gold
4712 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4713 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4716 /* Move from ICS was illegal!? Punt. */
4717 if (appData.debugMode) {
4718 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4719 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4721 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4722 strcat(parseList[moveNum - 1], " ");
4723 strcat(parseList[moveNum - 1], elapsed_time);
4724 moveList[moveNum - 1][0] = NULLCHAR;
4725 fromX = fromY = toX = toY = -1;
4728 if (appData.debugMode) {
4729 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4730 setbuf(debugFP, NULL);
4734 /* Send move to chess program (BEFORE animating it). */
4735 if (appData.zippyPlay && !newGame && newMove &&
4736 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4738 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4739 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4740 if (moveList[moveNum - 1][0] == NULLCHAR) {
4741 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4743 DisplayError(str, 0);
4745 if (first.sendTime) {
4746 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4748 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4749 if (firstMove && !bookHit) {
4751 if (first.useColors) {
4752 SendToProgram(gameMode == IcsPlayingWhite ?
4754 "black\ngo\n", &first);
4756 SendToProgram("go\n", &first);
4758 first.maybeThinking = TRUE;
4761 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4762 if (moveList[moveNum - 1][0] == NULLCHAR) {
4763 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4764 DisplayError(str, 0);
4766 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4767 SendMoveToProgram(moveNum - 1, &first);
4774 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4775 /* If move comes from a remote source, animate it. If it
4776 isn't remote, it will have already been animated. */
4777 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4778 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4780 if (!pausing && appData.highlightLastMove) {
4781 SetHighlights(fromX, fromY, toX, toY);
4785 /* Start the clocks */
4786 whiteFlag = blackFlag = FALSE;
4787 appData.clockMode = !(basetime == 0 && increment == 0);
4789 ics_clock_paused = TRUE;
4791 } else if (ticking == 1) {
4792 ics_clock_paused = FALSE;
4794 if (gameMode == IcsIdle ||
4795 relation == RELATION_OBSERVING_STATIC ||
4796 relation == RELATION_EXAMINING ||
4798 DisplayBothClocks();
4802 /* Display opponents and material strengths */
4803 if (gameInfo.variant != VariantBughouse &&
4804 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4805 if (tinyLayout || smallLayout) {
4806 if(gameInfo.variant == VariantNormal)
4807 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4808 gameInfo.white, white_stren, gameInfo.black, black_stren,
4809 basetime, increment);
4811 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4812 gameInfo.white, white_stren, gameInfo.black, black_stren,
4813 basetime, increment, (int) gameInfo.variant);
4815 if(gameInfo.variant == VariantNormal)
4816 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4817 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4818 basetime, increment);
4820 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4821 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4822 basetime, increment, VariantName(gameInfo.variant));
4825 if (appData.debugMode) {
4826 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4831 /* Display the board */
4832 if (!pausing && !appData.noGUI) {
4834 if (appData.premove)
4836 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4837 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4838 ClearPremoveHighlights();
4840 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4841 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4842 DrawPosition(j, boards[currentMove]);
4844 DisplayMove(moveNum - 1);
4845 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4846 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4847 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4848 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4852 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4854 if(bookHit) { // [HGM] book: simulate book reply
4855 static char bookMove[MSG_SIZ]; // a bit generous?
4857 programStats.nodes = programStats.depth = programStats.time =
4858 programStats.score = programStats.got_only_move = 0;
4859 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4861 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4862 strcat(bookMove, bookHit);
4863 HandleMachineMove(bookMove, &first);
4872 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4873 ics_getting_history = H_REQUESTED;
4874 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4880 AnalysisPeriodicEvent (int force)
4882 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4883 && !force) || !appData.periodicUpdates)
4886 /* Send . command to Crafty to collect stats */
4887 SendToProgram(".\n", &first);
4889 /* Don't send another until we get a response (this makes
4890 us stop sending to old Crafty's which don't understand
4891 the "." command (sending illegal cmds resets node count & time,
4892 which looks bad)) */
4893 programStats.ok_to_send = 0;
4897 ics_update_width (int new_width)
4899 ics_printf("set width %d\n", new_width);
4903 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4907 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4908 // null move in variant where engine does not understand it (for analysis purposes)
4909 SendBoard(cps, moveNum + 1); // send position after move in stead.
4912 if (cps->useUsermove) {
4913 SendToProgram("usermove ", cps);
4917 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4918 int len = space - parseList[moveNum];
4919 memcpy(buf, parseList[moveNum], len);
4921 buf[len] = NULLCHAR;
4923 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4925 SendToProgram(buf, cps);
4927 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4928 AlphaRank(moveList[moveNum], 4);
4929 SendToProgram(moveList[moveNum], cps);
4930 AlphaRank(moveList[moveNum], 4); // and back
4932 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4933 * the engine. It would be nice to have a better way to identify castle
4935 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4936 && cps->useOOCastle) {
4937 int fromX = moveList[moveNum][0] - AAA;
4938 int fromY = moveList[moveNum][1] - ONE;
4939 int toX = moveList[moveNum][2] - AAA;
4940 int toY = moveList[moveNum][3] - ONE;
4941 if((boards[moveNum][fromY][fromX] == WhiteKing
4942 && boards[moveNum][toY][toX] == WhiteRook)
4943 || (boards[moveNum][fromY][fromX] == BlackKing
4944 && boards[moveNum][toY][toX] == BlackRook)) {
4945 if(toX > fromX) SendToProgram("O-O\n", cps);
4946 else SendToProgram("O-O-O\n", cps);
4948 else SendToProgram(moveList[moveNum], cps);
4950 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4951 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4952 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4953 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4954 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4956 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4957 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4958 SendToProgram(buf, cps);
4960 else SendToProgram(moveList[moveNum], cps);
4961 /* End of additions by Tord */
4964 /* [HGM] setting up the opening has brought engine in force mode! */
4965 /* Send 'go' if we are in a mode where machine should play. */
4966 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4967 (gameMode == TwoMachinesPlay ||
4969 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4971 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4972 SendToProgram("go\n", cps);
4973 if (appData.debugMode) {
4974 fprintf(debugFP, "(extra)\n");
4977 setboardSpoiledMachineBlack = 0;
4981 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4983 char user_move[MSG_SIZ];
4986 if(gameInfo.variant == VariantSChess && promoChar) {
4987 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4988 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4989 } else suffix[0] = NULLCHAR;
4993 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4994 (int)moveType, fromX, fromY, toX, toY);
4995 DisplayError(user_move + strlen("say "), 0);
4997 case WhiteKingSideCastle:
4998 case BlackKingSideCastle:
4999 case WhiteQueenSideCastleWild:
5000 case BlackQueenSideCastleWild:
5002 case WhiteHSideCastleFR:
5003 case BlackHSideCastleFR:
5005 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5007 case WhiteQueenSideCastle:
5008 case BlackQueenSideCastle:
5009 case WhiteKingSideCastleWild:
5010 case BlackKingSideCastleWild:
5012 case WhiteASideCastleFR:
5013 case BlackASideCastleFR:
5015 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5017 case WhiteNonPromotion:
5018 case BlackNonPromotion:
5019 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5021 case WhitePromotion:
5022 case BlackPromotion:
5023 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5024 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5026 PieceToChar(WhiteFerz));
5027 else if(gameInfo.variant == VariantGreat)
5028 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5029 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5030 PieceToChar(WhiteMan));
5032 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5033 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5039 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5040 ToUpper(PieceToChar((ChessSquare) fromX)),
5041 AAA + toX, ONE + toY);
5043 case IllegalMove: /* could be a variant we don't quite understand */
5044 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5046 case WhiteCapturesEnPassant:
5047 case BlackCapturesEnPassant:
5048 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5049 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5052 SendToICS(user_move);
5053 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5054 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5059 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5060 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5061 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5062 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5063 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5066 if(gameMode != IcsExamining) { // is this ever not the case?
5067 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5069 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5070 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5071 } else { // on FICS we must first go to general examine mode
5072 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5074 if(gameInfo.variant != VariantNormal) {
5075 // try figure out wild number, as xboard names are not always valid on ICS
5076 for(i=1; i<=36; i++) {
5077 snprintf(buf, MSG_SIZ, "wild/%d", i);
5078 if(StringToVariant(buf) == gameInfo.variant) break;
5080 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5081 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5082 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5083 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5084 SendToICS(ics_prefix);
5086 if(startedFromSetupPosition || backwardMostMove != 0) {
5087 fen = PositionToFEN(backwardMostMove, NULL);
5088 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5089 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5091 } else { // FICS: everything has to set by separate bsetup commands
5092 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5093 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5095 if(!WhiteOnMove(backwardMostMove)) {
5096 SendToICS("bsetup tomove black\n");
5098 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5099 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5101 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5102 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5104 i = boards[backwardMostMove][EP_STATUS];
5105 if(i >= 0) { // set e.p.
5106 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5112 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5113 SendToICS("bsetup done\n"); // switch to normal examining.
5115 for(i = backwardMostMove; i<last; i++) {
5117 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5120 SendToICS(ics_prefix);
5121 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5125 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5127 if (rf == DROP_RANK) {
5128 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5129 sprintf(move, "%c@%c%c\n",
5130 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5132 if (promoChar == 'x' || promoChar == NULLCHAR) {
5133 sprintf(move, "%c%c%c%c\n",
5134 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5136 sprintf(move, "%c%c%c%c%c\n",
5137 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5143 ProcessICSInitScript (FILE *f)
5147 while (fgets(buf, MSG_SIZ, f)) {
5148 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5155 static int lastX, lastY, selectFlag, dragging;
5160 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5161 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5162 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5163 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5164 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5165 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5168 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5169 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5170 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5171 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5172 if(!step) step = -1;
5173 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5174 appData.testLegality && (promoSweep == king ||
5175 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5177 int victim = boards[currentMove][toY][toX];
5178 boards[currentMove][toY][toX] = promoSweep;
5179 DrawPosition(FALSE, boards[currentMove]);
5180 boards[currentMove][toY][toX] = victim;
5182 ChangeDragPiece(promoSweep);
5186 PromoScroll (int x, int y)
5190 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5191 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5192 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5193 if(!step) return FALSE;
5194 lastX = x; lastY = y;
5195 if((promoSweep < BlackPawn) == flipView) step = -step;
5196 if(step > 0) selectFlag = 1;
5197 if(!selectFlag) Sweep(step);
5202 NextPiece (int step)
5204 ChessSquare piece = boards[currentMove][toY][toX];
5207 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5208 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5209 if(!step) step = -1;
5210 } while(PieceToChar(pieceSweep) == '.');
5211 boards[currentMove][toY][toX] = pieceSweep;
5212 DrawPosition(FALSE, boards[currentMove]);
5213 boards[currentMove][toY][toX] = piece;
5215 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5217 AlphaRank (char *move, int n)
5219 // char *p = move, c; int x, y;
5221 if (appData.debugMode) {
5222 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5226 move[2]>='0' && move[2]<='9' &&
5227 move[3]>='a' && move[3]<='x' ) {
5229 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5230 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5232 if(move[0]>='0' && move[0]<='9' &&
5233 move[1]>='a' && move[1]<='x' &&
5234 move[2]>='0' && move[2]<='9' &&
5235 move[3]>='a' && move[3]<='x' ) {
5236 /* input move, Shogi -> normal */
5237 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5238 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5239 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5240 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5243 move[3]>='0' && move[3]<='9' &&
5244 move[2]>='a' && move[2]<='x' ) {
5246 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5247 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5250 move[0]>='a' && move[0]<='x' &&
5251 move[3]>='0' && move[3]<='9' &&
5252 move[2]>='a' && move[2]<='x' ) {
5253 /* output move, normal -> Shogi */
5254 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5255 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5256 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5257 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5258 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5260 if (appData.debugMode) {
5261 fprintf(debugFP, " out = '%s'\n", move);
5265 char yy_textstr[8000];
5267 /* Parser for moves from gnuchess, ICS, or user typein box */
5269 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5271 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5273 switch (*moveType) {
5274 case WhitePromotion:
5275 case BlackPromotion:
5276 case WhiteNonPromotion:
5277 case BlackNonPromotion:
5279 case WhiteCapturesEnPassant:
5280 case BlackCapturesEnPassant:
5281 case WhiteKingSideCastle:
5282 case WhiteQueenSideCastle:
5283 case BlackKingSideCastle:
5284 case BlackQueenSideCastle:
5285 case WhiteKingSideCastleWild:
5286 case WhiteQueenSideCastleWild:
5287 case BlackKingSideCastleWild:
5288 case BlackQueenSideCastleWild:
5289 /* Code added by Tord: */
5290 case WhiteHSideCastleFR:
5291 case WhiteASideCastleFR:
5292 case BlackHSideCastleFR:
5293 case BlackASideCastleFR:
5294 /* End of code added by Tord */
5295 case IllegalMove: /* bug or odd chess variant */
5296 *fromX = currentMoveString[0] - AAA;
5297 *fromY = currentMoveString[1] - ONE;
5298 *toX = currentMoveString[2] - AAA;
5299 *toY = currentMoveString[3] - ONE;
5300 *promoChar = currentMoveString[4];
5301 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5302 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5303 if (appData.debugMode) {
5304 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5306 *fromX = *fromY = *toX = *toY = 0;
5309 if (appData.testLegality) {
5310 return (*moveType != IllegalMove);
5312 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5313 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5318 *fromX = *moveType == WhiteDrop ?
5319 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5320 (int) CharToPiece(ToLower(currentMoveString[0]));
5322 *toX = currentMoveString[2] - AAA;
5323 *toY = currentMoveString[3] - ONE;
5324 *promoChar = NULLCHAR;
5328 case ImpossibleMove:
5338 if (appData.debugMode) {
5339 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5342 *fromX = *fromY = *toX = *toY = 0;
5343 *promoChar = NULLCHAR;
5348 Boolean pushed = FALSE;
5349 char *lastParseAttempt;
5352 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5353 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5354 int fromX, fromY, toX, toY; char promoChar;
5359 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5360 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5363 endPV = forwardMostMove;
5365 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5366 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5367 lastParseAttempt = pv;
5368 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5369 if(!valid && nr == 0 &&
5370 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5371 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5372 // Hande case where played move is different from leading PV move
5373 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5374 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5375 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5376 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5377 endPV += 2; // if position different, keep this
5378 moveList[endPV-1][0] = fromX + AAA;
5379 moveList[endPV-1][1] = fromY + ONE;
5380 moveList[endPV-1][2] = toX + AAA;
5381 moveList[endPV-1][3] = toY + ONE;
5382 parseList[endPV-1][0] = NULLCHAR;
5383 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5386 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5387 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5388 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5389 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5390 valid++; // allow comments in PV
5394 if(endPV+1 > framePtr) break; // no space, truncate
5397 CopyBoard(boards[endPV], boards[endPV-1]);
5398 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5399 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5400 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5401 CoordsToAlgebraic(boards[endPV - 1],
5402 PosFlags(endPV - 1),
5403 fromY, fromX, toY, toX, promoChar,
5404 parseList[endPV - 1]);
5406 if(atEnd == 2) return; // used hidden, for PV conversion
5407 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5408 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5409 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5410 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5411 DrawPosition(TRUE, boards[currentMove]);
5415 MultiPV (ChessProgramState *cps)
5416 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5418 for(i=0; i<cps->nrOptions; i++)
5419 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5425 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5427 int startPV, multi, lineStart, origIndex = index;
5428 char *p, buf2[MSG_SIZ];
5430 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5431 lastX = x; lastY = y;
5432 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5433 lineStart = startPV = index;
5434 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5435 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5437 do{ while(buf[index] && buf[index] != '\n') index++;
5438 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5440 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5441 int n = first.option[multi].value;
5442 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5443 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5444 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5445 first.option[multi].value = n;
5448 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5449 ExcludeClick(origIndex - lineStart);
5452 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5453 *start = startPV; *end = index-1;
5460 static char buf[10*MSG_SIZ];
5461 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5463 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5464 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5465 for(i = forwardMostMove; i<endPV; i++){
5466 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5467 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5470 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5471 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5477 LoadPV (int x, int y)
5478 { // called on right mouse click to load PV
5479 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5480 lastX = x; lastY = y;
5481 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5488 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5489 if(endPV < 0) return;
5490 if(appData.autoCopyPV) CopyFENToClipboard();
5492 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5493 Boolean saveAnimate = appData.animate;
5495 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5496 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5497 } else storedGames--; // abandon shelved tail of original game
5500 forwardMostMove = currentMove;
5501 currentMove = oldFMM;
5502 appData.animate = FALSE;
5503 ToNrEvent(forwardMostMove);
5504 appData.animate = saveAnimate;
5506 currentMove = forwardMostMove;
5507 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5508 ClearPremoveHighlights();
5509 DrawPosition(TRUE, boards[currentMove]);
5513 MovePV (int x, int y, int h)
5514 { // step through PV based on mouse coordinates (called on mouse move)
5515 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5517 // we must somehow check if right button is still down (might be released off board!)
5518 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5519 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5520 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5522 lastX = x; lastY = y;
5524 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5525 if(endPV < 0) return;
5526 if(y < margin) step = 1; else
5527 if(y > h - margin) step = -1;
5528 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5529 currentMove += step;
5530 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5531 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5532 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5533 DrawPosition(FALSE, boards[currentMove]);
5537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5538 // All positions will have equal probability, but the current method will not provide a unique
5539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5545 int piecesLeft[(int)BlackPawn];
5546 int seed, nrOfShuffles;
5549 GetPositionNumber ()
5550 { // sets global variable seed
5553 seed = appData.defaultFrcPosition;
5554 if(seed < 0) { // randomize based on time for negative FRC position numbers
5555 for(i=0; i<50; i++) seed += random();
5556 seed = random() ^ random() >> 8 ^ random() << 8;
5557 if(seed<0) seed = -seed;
5562 put (Board board, int pieceType, int rank, int n, int shade)
5563 // put the piece on the (n-1)-th empty squares of the given shade
5567 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5568 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5569 board[rank][i] = (ChessSquare) pieceType;
5570 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5572 piecesLeft[pieceType]--;
5581 AddOnePiece (Board board, int pieceType, int rank, int shade)
5582 // calculate where the next piece goes, (any empty square), and put it there
5586 i = seed % squaresLeft[shade];
5587 nrOfShuffles *= squaresLeft[shade];
5588 seed /= squaresLeft[shade];
5589 put(board, pieceType, rank, i, shade);
5593 AddTwoPieces (Board board, int pieceType, int rank)
5594 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5596 int i, n=squaresLeft[ANY], j=n-1, k;
5598 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5599 i = seed % k; // pick one
5602 while(i >= j) i -= j--;
5603 j = n - 1 - j; i += j;
5604 put(board, pieceType, rank, j, ANY);
5605 put(board, pieceType, rank, i, ANY);
5609 SetUpShuffle (Board board, int number)
5613 GetPositionNumber(); nrOfShuffles = 1;
5615 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5616 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5617 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5619 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5621 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5622 p = (int) board[0][i];
5623 if(p < (int) BlackPawn) piecesLeft[p] ++;
5624 board[0][i] = EmptySquare;
5627 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5628 // shuffles restricted to allow normal castling put KRR first
5629 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5630 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5631 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5632 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5633 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5634 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5635 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5636 put(board, WhiteRook, 0, 0, ANY);
5637 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5640 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5641 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5642 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5643 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5644 while(piecesLeft[p] >= 2) {
5645 AddOnePiece(board, p, 0, LITE);
5646 AddOnePiece(board, p, 0, DARK);
5648 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5651 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5652 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5653 // but we leave King and Rooks for last, to possibly obey FRC restriction
5654 if(p == (int)WhiteRook) continue;
5655 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5656 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5659 // now everything is placed, except perhaps King (Unicorn) and Rooks
5661 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5662 // Last King gets castling rights
5663 while(piecesLeft[(int)WhiteUnicorn]) {
5664 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5665 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5668 while(piecesLeft[(int)WhiteKing]) {
5669 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5670 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5675 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5676 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5679 // Only Rooks can be left; simply place them all
5680 while(piecesLeft[(int)WhiteRook]) {
5681 i = put(board, WhiteRook, 0, 0, ANY);
5682 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5685 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5687 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5690 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5691 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5694 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5698 SetCharTable (char *table, const char * map)
5699 /* [HGM] moved here from winboard.c because of its general usefulness */
5700 /* Basically a safe strcpy that uses the last character as King */
5702 int result = FALSE; int NrPieces;
5704 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5705 && NrPieces >= 12 && !(NrPieces&1)) {
5706 int i; /* [HGM] Accept even length from 12 to 34 */
5708 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5709 for( i=0; i<NrPieces/2-1; i++ ) {
5711 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5713 table[(int) WhiteKing] = map[NrPieces/2-1];
5714 table[(int) BlackKing] = map[NrPieces-1];
5723 Prelude (Board board)
5724 { // [HGM] superchess: random selection of exo-pieces
5725 int i, j, k; ChessSquare p;
5726 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5728 GetPositionNumber(); // use FRC position number
5730 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5731 SetCharTable(pieceToChar, appData.pieceToCharTable);
5732 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5733 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5736 j = seed%4; seed /= 4;
5737 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5738 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5739 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5740 j = seed%3 + (seed%3 >= j); seed /= 3;
5741 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5742 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5743 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5744 j = seed%3; seed /= 3;
5745 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5746 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5747 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5748 j = seed%2 + (seed%2 >= j); seed /= 2;
5749 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5750 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5751 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5752 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5753 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5754 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5755 put(board, exoPieces[0], 0, 0, ANY);
5756 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5760 InitPosition (int redraw)
5762 ChessSquare (* pieces)[BOARD_FILES];
5763 int i, j, pawnRow, overrule,
5764 oldx = gameInfo.boardWidth,
5765 oldy = gameInfo.boardHeight,
5766 oldh = gameInfo.holdingsWidth;
5769 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5771 /* [AS] Initialize pv info list [HGM] and game status */
5773 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5774 pvInfoList[i].depth = 0;
5775 boards[i][EP_STATUS] = EP_NONE;
5776 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5779 initialRulePlies = 0; /* 50-move counter start */
5781 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5782 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5786 /* [HGM] logic here is completely changed. In stead of full positions */
5787 /* the initialized data only consist of the two backranks. The switch */
5788 /* selects which one we will use, which is than copied to the Board */
5789 /* initialPosition, which for the rest is initialized by Pawns and */
5790 /* empty squares. This initial position is then copied to boards[0], */
5791 /* possibly after shuffling, so that it remains available. */
5793 gameInfo.holdingsWidth = 0; /* default board sizes */
5794 gameInfo.boardWidth = 8;
5795 gameInfo.boardHeight = 8;
5796 gameInfo.holdingsSize = 0;
5797 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5798 for(i=0; i<BOARD_FILES-2; i++)
5799 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5800 initialPosition[EP_STATUS] = EP_NONE;
5801 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5802 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5803 SetCharTable(pieceNickName, appData.pieceNickNames);
5804 else SetCharTable(pieceNickName, "............");
5807 switch (gameInfo.variant) {
5808 case VariantFischeRandom:
5809 shuffleOpenings = TRUE;
5812 case VariantShatranj:
5813 pieces = ShatranjArray;
5814 nrCastlingRights = 0;
5815 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5818 pieces = makrukArray;
5819 nrCastlingRights = 0;
5820 startedFromSetupPosition = TRUE;
5821 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5823 case VariantTwoKings:
5824 pieces = twoKingsArray;
5827 pieces = GrandArray;
5828 nrCastlingRights = 0;
5829 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5830 gameInfo.boardWidth = 10;
5831 gameInfo.boardHeight = 10;
5832 gameInfo.holdingsSize = 7;
5834 case VariantCapaRandom:
5835 shuffleOpenings = TRUE;
5836 case VariantCapablanca:
5837 pieces = CapablancaArray;
5838 gameInfo.boardWidth = 10;
5839 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5842 pieces = GothicArray;
5843 gameInfo.boardWidth = 10;
5844 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5847 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5848 gameInfo.holdingsSize = 7;
5851 pieces = JanusArray;
5852 gameInfo.boardWidth = 10;
5853 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5854 nrCastlingRights = 6;
5855 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5856 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5857 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5858 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5859 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5860 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5863 pieces = FalconArray;
5864 gameInfo.boardWidth = 10;
5865 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5867 case VariantXiangqi:
5868 pieces = XiangqiArray;
5869 gameInfo.boardWidth = 9;
5870 gameInfo.boardHeight = 10;
5871 nrCastlingRights = 0;
5872 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5875 pieces = ShogiArray;
5876 gameInfo.boardWidth = 9;
5877 gameInfo.boardHeight = 9;
5878 gameInfo.holdingsSize = 7;
5879 nrCastlingRights = 0;
5880 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5882 case VariantCourier:
5883 pieces = CourierArray;
5884 gameInfo.boardWidth = 12;
5885 nrCastlingRights = 0;
5886 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5888 case VariantKnightmate:
5889 pieces = KnightmateArray;
5890 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5892 case VariantSpartan:
5893 pieces = SpartanArray;
5894 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5897 pieces = fairyArray;
5898 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5901 pieces = GreatArray;
5902 gameInfo.boardWidth = 10;
5903 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5904 gameInfo.holdingsSize = 8;
5908 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5909 gameInfo.holdingsSize = 8;
5910 startedFromSetupPosition = TRUE;
5912 case VariantCrazyhouse:
5913 case VariantBughouse:
5915 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5916 gameInfo.holdingsSize = 5;
5918 case VariantWildCastle:
5920 /* !!?shuffle with kings guaranteed to be on d or e file */
5921 shuffleOpenings = 1;
5923 case VariantNoCastle:
5925 nrCastlingRights = 0;
5926 /* !!?unconstrained back-rank shuffle */
5927 shuffleOpenings = 1;
5932 if(appData.NrFiles >= 0) {
5933 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5934 gameInfo.boardWidth = appData.NrFiles;
5936 if(appData.NrRanks >= 0) {
5937 gameInfo.boardHeight = appData.NrRanks;
5939 if(appData.holdingsSize >= 0) {
5940 i = appData.holdingsSize;
5941 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5942 gameInfo.holdingsSize = i;
5944 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5945 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5946 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5948 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5949 if(pawnRow < 1) pawnRow = 1;
5950 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5952 /* User pieceToChar list overrules defaults */
5953 if(appData.pieceToCharTable != NULL)
5954 SetCharTable(pieceToChar, appData.pieceToCharTable);
5956 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5958 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5959 s = (ChessSquare) 0; /* account holding counts in guard band */
5960 for( i=0; i<BOARD_HEIGHT; i++ )
5961 initialPosition[i][j] = s;
5963 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5964 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5965 initialPosition[pawnRow][j] = WhitePawn;
5966 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5967 if(gameInfo.variant == VariantXiangqi) {
5969 initialPosition[pawnRow][j] =
5970 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5971 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5972 initialPosition[2][j] = WhiteCannon;
5973 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5977 if(gameInfo.variant == VariantGrand) {
5978 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5979 initialPosition[0][j] = WhiteRook;
5980 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5983 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5985 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5988 initialPosition[1][j] = WhiteBishop;
5989 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5991 initialPosition[1][j] = WhiteRook;
5992 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5995 if( nrCastlingRights == -1) {
5996 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5997 /* This sets default castling rights from none to normal corners */
5998 /* Variants with other castling rights must set them themselves above */
5999 nrCastlingRights = 6;
6001 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6002 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6003 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6004 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6005 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6006 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6009 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6010 if(gameInfo.variant == VariantGreat) { // promotion commoners
6011 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6012 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6013 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6014 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6016 if( gameInfo.variant == VariantSChess ) {
6017 initialPosition[1][0] = BlackMarshall;
6018 initialPosition[2][0] = BlackAngel;
6019 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6020 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6021 initialPosition[1][1] = initialPosition[2][1] =
6022 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6024 if (appData.debugMode) {
6025 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6027 if(shuffleOpenings) {
6028 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6029 startedFromSetupPosition = TRUE;
6031 if(startedFromPositionFile) {
6032 /* [HGM] loadPos: use PositionFile for every new game */
6033 CopyBoard(initialPosition, filePosition);
6034 for(i=0; i<nrCastlingRights; i++)
6035 initialRights[i] = filePosition[CASTLING][i];
6036 startedFromSetupPosition = TRUE;
6039 CopyBoard(boards[0], initialPosition);
6041 if(oldx != gameInfo.boardWidth ||
6042 oldy != gameInfo.boardHeight ||
6043 oldv != gameInfo.variant ||
6044 oldh != gameInfo.holdingsWidth
6046 InitDrawingSizes(-2 ,0);
6048 oldv = gameInfo.variant;
6050 DrawPosition(TRUE, boards[currentMove]);
6054 SendBoard (ChessProgramState *cps, int moveNum)
6056 char message[MSG_SIZ];
6058 if (cps->useSetboard) {
6059 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6060 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6061 SendToProgram(message, cps);
6066 int i, j, left=0, right=BOARD_WIDTH;
6067 /* Kludge to set black to move, avoiding the troublesome and now
6068 * deprecated "black" command.
6070 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6071 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6073 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6075 SendToProgram("edit\n", cps);
6076 SendToProgram("#\n", cps);
6077 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6078 bp = &boards[moveNum][i][left];
6079 for (j = left; j < right; j++, bp++) {
6080 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6081 if ((int) *bp < (int) BlackPawn) {
6082 if(j == BOARD_RGHT+1)
6083 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6084 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6085 if(message[0] == '+' || message[0] == '~') {
6086 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6087 PieceToChar((ChessSquare)(DEMOTED *bp)),
6090 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6091 message[1] = BOARD_RGHT - 1 - j + '1';
6092 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6094 SendToProgram(message, cps);
6099 SendToProgram("c\n", cps);
6100 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6101 bp = &boards[moveNum][i][left];
6102 for (j = left; j < right; j++, bp++) {
6103 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6104 if (((int) *bp != (int) EmptySquare)
6105 && ((int) *bp >= (int) BlackPawn)) {
6106 if(j == BOARD_LEFT-2)
6107 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6108 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6110 if(message[0] == '+' || message[0] == '~') {
6111 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6112 PieceToChar((ChessSquare)(DEMOTED *bp)),
6115 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6116 message[1] = BOARD_RGHT - 1 - j + '1';
6117 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6119 SendToProgram(message, cps);
6124 SendToProgram(".\n", cps);
6126 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6129 char exclusionHeader[MSG_SIZ];
6130 int exCnt, excludePtr;
6131 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6132 static Exclusion excluTab[200];
6133 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6139 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6140 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6146 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6147 excludePtr = 24; exCnt = 0;
6152 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6153 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6154 char buf[2*MOVE_LEN], *p;
6155 Exclusion *e = excluTab;
6157 for(i=0; i<exCnt; i++)
6158 if(e[i].ff == fromX && e[i].fr == fromY &&
6159 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6160 if(i == exCnt) { // was not in exclude list; add it
6161 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6162 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6163 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6166 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6167 excludePtr++; e[i].mark = excludePtr++;
6168 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6171 exclusionHeader[e[i].mark] = state;
6175 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6176 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6180 if(promoChar == -1) { // kludge to indicate best move
6181 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6182 return 1; // if unparsable, abort
6184 // update exclusion map (resolving toggle by consulting existing state)
6185 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6187 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6188 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6189 excludeMap[k] |= 1<<j;
6190 else excludeMap[k] &= ~(1<<j);
6192 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6194 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6195 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6196 SendToProgram(buf, &first);
6197 return (state == '+');
6201 ExcludeClick (int index)
6204 Exclusion *e = excluTab;
6205 if(index < 25) { // none, best or tail clicked
6206 if(index < 13) { // none: include all
6207 WriteMap(0); // clear map
6208 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6209 SendToProgram("include all\n", &first); // and inform engine
6210 } else if(index > 18) { // tail
6211 if(exclusionHeader[19] == '-') { // tail was excluded
6212 SendToProgram("include all\n", &first);
6213 WriteMap(0); // clear map completely
6214 // now re-exclude selected moves
6215 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6216 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6217 } else { // tail was included or in mixed state
6218 SendToProgram("exclude all\n", &first);
6219 WriteMap(0xFF); // fill map completely
6220 // now re-include selected moves
6221 j = 0; // count them
6222 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6223 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6224 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6227 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6230 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6231 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6232 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6239 DefaultPromoChoice (int white)
6242 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6243 result = WhiteFerz; // no choice
6244 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6245 result= WhiteKing; // in Suicide Q is the last thing we want
6246 else if(gameInfo.variant == VariantSpartan)
6247 result = white ? WhiteQueen : WhiteAngel;
6248 else result = WhiteQueen;
6249 if(!white) result = WHITE_TO_BLACK result;
6253 static int autoQueen; // [HGM] oneclick
6256 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6258 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6259 /* [HGM] add Shogi promotions */
6260 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6265 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6266 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6268 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6269 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6272 piece = boards[currentMove][fromY][fromX];
6273 if(gameInfo.variant == VariantShogi) {
6274 promotionZoneSize = BOARD_HEIGHT/3;
6275 highestPromotingPiece = (int)WhiteFerz;
6276 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6277 promotionZoneSize = 3;
6280 // Treat Lance as Pawn when it is not representing Amazon
6281 if(gameInfo.variant != VariantSuper) {
6282 if(piece == WhiteLance) piece = WhitePawn; else
6283 if(piece == BlackLance) piece = BlackPawn;
6286 // next weed out all moves that do not touch the promotion zone at all
6287 if((int)piece >= BlackPawn) {
6288 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6290 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6292 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6293 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6296 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6298 // weed out mandatory Shogi promotions
6299 if(gameInfo.variant == VariantShogi) {
6300 if(piece >= BlackPawn) {
6301 if(toY == 0 && piece == BlackPawn ||
6302 toY == 0 && piece == BlackQueen ||
6303 toY <= 1 && piece == BlackKnight) {
6308 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6309 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6310 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6317 // weed out obviously illegal Pawn moves
6318 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6319 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6320 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6321 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6322 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6323 // note we are not allowed to test for valid (non-)capture, due to premove
6326 // we either have a choice what to promote to, or (in Shogi) whether to promote
6327 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6328 *promoChoice = PieceToChar(BlackFerz); // no choice
6331 // no sense asking what we must promote to if it is going to explode...
6332 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6333 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6336 // give caller the default choice even if we will not make it
6337 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6338 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6339 if( sweepSelect && gameInfo.variant != VariantGreat
6340 && gameInfo.variant != VariantGrand
6341 && gameInfo.variant != VariantSuper) return FALSE;
6342 if(autoQueen) return FALSE; // predetermined
6344 // suppress promotion popup on illegal moves that are not premoves
6345 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6346 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6347 if(appData.testLegality && !premove) {
6348 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6349 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6350 if(moveType != WhitePromotion && moveType != BlackPromotion)
6358 InPalace (int row, int column)
6359 { /* [HGM] for Xiangqi */
6360 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6361 column < (BOARD_WIDTH + 4)/2 &&
6362 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6367 PieceForSquare (int x, int y)
6369 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6372 return boards[currentMove][y][x];
6376 OKToStartUserMove (int x, int y)
6378 ChessSquare from_piece;
6381 if (matchMode) return FALSE;
6382 if (gameMode == EditPosition) return TRUE;
6384 if (x >= 0 && y >= 0)
6385 from_piece = boards[currentMove][y][x];
6387 from_piece = EmptySquare;
6389 if (from_piece == EmptySquare) return FALSE;
6391 white_piece = (int)from_piece >= (int)WhitePawn &&
6392 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6396 case TwoMachinesPlay:
6404 case MachinePlaysWhite:
6405 case IcsPlayingBlack:
6406 if (appData.zippyPlay) return FALSE;
6408 DisplayMoveError(_("You are playing Black"));
6413 case MachinePlaysBlack:
6414 case IcsPlayingWhite:
6415 if (appData.zippyPlay) return FALSE;
6417 DisplayMoveError(_("You are playing White"));
6422 case PlayFromGameFile:
6423 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6425 if (!white_piece && WhiteOnMove(currentMove)) {
6426 DisplayMoveError(_("It is White's turn"));
6429 if (white_piece && !WhiteOnMove(currentMove)) {
6430 DisplayMoveError(_("It is Black's turn"));
6433 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6434 /* Editing correspondence game history */
6435 /* Could disallow this or prompt for confirmation */
6440 case BeginningOfGame:
6441 if (appData.icsActive) return FALSE;
6442 if (!appData.noChessProgram) {
6444 DisplayMoveError(_("You are playing White"));
6451 if (!white_piece && WhiteOnMove(currentMove)) {
6452 DisplayMoveError(_("It is White's turn"));
6455 if (white_piece && !WhiteOnMove(currentMove)) {
6456 DisplayMoveError(_("It is Black's turn"));
6465 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6466 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6467 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6468 && gameMode != AnalyzeFile && gameMode != Training) {
6469 DisplayMoveError(_("Displayed position is not current"));
6476 OnlyMove (int *x, int *y, Boolean captures)
6478 DisambiguateClosure cl;
6479 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6481 case MachinePlaysBlack:
6482 case IcsPlayingWhite:
6483 case BeginningOfGame:
6484 if(!WhiteOnMove(currentMove)) return FALSE;
6486 case MachinePlaysWhite:
6487 case IcsPlayingBlack:
6488 if(WhiteOnMove(currentMove)) return FALSE;
6495 cl.pieceIn = EmptySquare;
6500 cl.promoCharIn = NULLCHAR;
6501 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6502 if( cl.kind == NormalMove ||
6503 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6504 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6505 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6512 if(cl.kind != ImpossibleMove) return FALSE;
6513 cl.pieceIn = EmptySquare;
6518 cl.promoCharIn = NULLCHAR;
6519 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6520 if( cl.kind == NormalMove ||
6521 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6522 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6523 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6528 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6534 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6535 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6536 int lastLoadGameUseList = FALSE;
6537 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6538 ChessMove lastLoadGameStart = EndOfFile;
6542 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6545 ChessSquare pdown, pup;
6546 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6549 /* Check if the user is playing in turn. This is complicated because we
6550 let the user "pick up" a piece before it is his turn. So the piece he
6551 tried to pick up may have been captured by the time he puts it down!
6552 Therefore we use the color the user is supposed to be playing in this
6553 test, not the color of the piece that is currently on the starting
6554 square---except in EditGame mode, where the user is playing both
6555 sides; fortunately there the capture race can't happen. (It can
6556 now happen in IcsExamining mode, but that's just too bad. The user
6557 will get a somewhat confusing message in that case.)
6562 case TwoMachinesPlay:
6566 /* We switched into a game mode where moves are not accepted,
6567 perhaps while the mouse button was down. */
6570 case MachinePlaysWhite:
6571 /* User is moving for Black */
6572 if (WhiteOnMove(currentMove)) {
6573 DisplayMoveError(_("It is White's turn"));
6578 case MachinePlaysBlack:
6579 /* User is moving for White */
6580 if (!WhiteOnMove(currentMove)) {
6581 DisplayMoveError(_("It is Black's turn"));
6586 case PlayFromGameFile:
6587 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6590 case BeginningOfGame:
6593 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6594 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6595 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6596 /* User is moving for Black */
6597 if (WhiteOnMove(currentMove)) {
6598 DisplayMoveError(_("It is White's turn"));
6602 /* User is moving for White */
6603 if (!WhiteOnMove(currentMove)) {
6604 DisplayMoveError(_("It is Black's turn"));
6610 case IcsPlayingBlack:
6611 /* User is moving for Black */
6612 if (WhiteOnMove(currentMove)) {
6613 if (!appData.premove) {
6614 DisplayMoveError(_("It is White's turn"));
6615 } else if (toX >= 0 && toY >= 0) {
6618 premoveFromX = fromX;
6619 premoveFromY = fromY;
6620 premovePromoChar = promoChar;
6622 if (appData.debugMode)
6623 fprintf(debugFP, "Got premove: fromX %d,"
6624 "fromY %d, toX %d, toY %d\n",
6625 fromX, fromY, toX, toY);
6631 case IcsPlayingWhite:
6632 /* User is moving for White */
6633 if (!WhiteOnMove(currentMove)) {
6634 if (!appData.premove) {
6635 DisplayMoveError(_("It is Black's turn"));
6636 } else if (toX >= 0 && toY >= 0) {
6639 premoveFromX = fromX;
6640 premoveFromY = fromY;
6641 premovePromoChar = promoChar;
6643 if (appData.debugMode)
6644 fprintf(debugFP, "Got premove: fromX %d,"
6645 "fromY %d, toX %d, toY %d\n",
6646 fromX, fromY, toX, toY);
6656 /* EditPosition, empty square, or different color piece;
6657 click-click move is possible */
6658 if (toX == -2 || toY == -2) {
6659 boards[0][fromY][fromX] = EmptySquare;
6660 DrawPosition(FALSE, boards[currentMove]);
6662 } else if (toX >= 0 && toY >= 0) {
6663 boards[0][toY][toX] = boards[0][fromY][fromX];
6664 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6665 if(boards[0][fromY][0] != EmptySquare) {
6666 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6667 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6670 if(fromX == BOARD_RGHT+1) {
6671 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6672 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6673 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6676 boards[0][fromY][fromX] = gatingPiece;
6677 DrawPosition(FALSE, boards[currentMove]);
6683 if(toX < 0 || toY < 0) return;
6684 pdown = boards[currentMove][fromY][fromX];
6685 pup = boards[currentMove][toY][toX];
6687 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6688 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6689 if( pup != EmptySquare ) return;
6690 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6691 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6692 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6693 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6694 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6695 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6696 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6700 /* [HGM] always test for legality, to get promotion info */
6701 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6702 fromY, fromX, toY, toX, promoChar);
6704 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6706 /* [HGM] but possibly ignore an IllegalMove result */
6707 if (appData.testLegality) {
6708 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6709 DisplayMoveError(_("Illegal move"));
6714 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6715 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6716 ClearPremoveHighlights(); // was included
6717 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6721 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6724 /* Common tail of UserMoveEvent and DropMenuEvent */
6726 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6730 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6731 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6732 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6733 if(WhiteOnMove(currentMove)) {
6734 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6736 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6740 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6741 move type in caller when we know the move is a legal promotion */
6742 if(moveType == NormalMove && promoChar)
6743 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6745 /* [HGM] <popupFix> The following if has been moved here from
6746 UserMoveEvent(). Because it seemed to belong here (why not allow
6747 piece drops in training games?), and because it can only be
6748 performed after it is known to what we promote. */
6749 if (gameMode == Training) {
6750 /* compare the move played on the board to the next move in the
6751 * game. If they match, display the move and the opponent's response.
6752 * If they don't match, display an error message.
6756 CopyBoard(testBoard, boards[currentMove]);
6757 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6759 if (CompareBoards(testBoard, boards[currentMove+1])) {
6760 ForwardInner(currentMove+1);
6762 /* Autoplay the opponent's response.
6763 * if appData.animate was TRUE when Training mode was entered,
6764 * the response will be animated.
6766 saveAnimate = appData.animate;
6767 appData.animate = animateTraining;
6768 ForwardInner(currentMove+1);
6769 appData.animate = saveAnimate;
6771 /* check for the end of the game */
6772 if (currentMove >= forwardMostMove) {
6773 gameMode = PlayFromGameFile;
6775 SetTrainingModeOff();
6776 DisplayInformation(_("End of game"));
6779 DisplayError(_("Incorrect move"), 0);
6784 /* Ok, now we know that the move is good, so we can kill
6785 the previous line in Analysis Mode */
6786 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6787 && currentMove < forwardMostMove) {
6788 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6789 else forwardMostMove = currentMove;
6794 /* If we need the chess program but it's dead, restart it */
6795 ResurrectChessProgram();
6797 /* A user move restarts a paused game*/
6801 thinkOutput[0] = NULLCHAR;
6803 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6805 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6806 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6810 if (gameMode == BeginningOfGame) {
6811 if (appData.noChessProgram) {
6812 gameMode = EditGame;
6816 gameMode = MachinePlaysBlack;
6819 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6821 if (first.sendName) {
6822 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6823 SendToProgram(buf, &first);
6830 /* Relay move to ICS or chess engine */
6831 if (appData.icsActive) {
6832 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6833 gameMode == IcsExamining) {
6834 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6835 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6837 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6839 // also send plain move, in case ICS does not understand atomic claims
6840 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6844 if (first.sendTime && (gameMode == BeginningOfGame ||
6845 gameMode == MachinePlaysWhite ||
6846 gameMode == MachinePlaysBlack)) {
6847 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6849 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6850 // [HGM] book: if program might be playing, let it use book
6851 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6852 first.maybeThinking = TRUE;
6853 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6854 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6855 SendBoard(&first, currentMove+1);
6856 } else SendMoveToProgram(forwardMostMove-1, &first);
6857 if (currentMove == cmailOldMove + 1) {
6858 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6862 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6866 if(appData.testLegality)
6867 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6873 if (WhiteOnMove(currentMove)) {
6874 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6876 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6880 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6885 case MachinePlaysBlack:
6886 case MachinePlaysWhite:
6887 /* disable certain menu options while machine is thinking */
6888 SetMachineThinkingEnables();
6895 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6896 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6898 if(bookHit) { // [HGM] book: simulate book reply
6899 static char bookMove[MSG_SIZ]; // a bit generous?
6901 programStats.nodes = programStats.depth = programStats.time =
6902 programStats.score = programStats.got_only_move = 0;
6903 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6905 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6906 strcat(bookMove, bookHit);
6907 HandleMachineMove(bookMove, &first);
6913 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6915 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6916 Markers *m = (Markers *) closure;
6917 if(rf == fromY && ff == fromX)
6918 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6919 || kind == WhiteCapturesEnPassant
6920 || kind == BlackCapturesEnPassant);
6921 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6925 MarkTargetSquares (int clear)
6928 if(clear) // no reason to ever suppress clearing
6929 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6930 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6931 !appData.testLegality || gameMode == EditPosition) return;
6934 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6935 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6936 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6938 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6941 DrawPosition(FALSE, NULL);
6945 Explode (Board board, int fromX, int fromY, int toX, int toY)
6947 if(gameInfo.variant == VariantAtomic &&
6948 (board[toY][toX] != EmptySquare || // capture?
6949 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6950 board[fromY][fromX] == BlackPawn )
6952 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6958 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6961 CanPromote (ChessSquare piece, int y)
6963 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6964 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6965 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6966 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6967 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6968 gameInfo.variant == VariantMakruk) return FALSE;
6969 return (piece == BlackPawn && y == 1 ||
6970 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6971 piece == BlackLance && y == 1 ||
6972 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6976 LeftClick (ClickType clickType, int xPix, int yPix)
6979 Boolean saveAnimate;
6980 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
6981 char promoChoice = NULLCHAR;
6983 static TimeMark lastClickTime, prevClickTime;
6985 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6987 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6989 if (clickType == Press) ErrorPopDown();
6991 x = EventToSquare(xPix, BOARD_WIDTH);
6992 y = EventToSquare(yPix, BOARD_HEIGHT);
6993 if (!flipView && y >= 0) {
6994 y = BOARD_HEIGHT - 1 - y;
6996 if (flipView && x >= 0) {
6997 x = BOARD_WIDTH - 1 - x;
7000 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7001 defaultPromoChoice = promoSweep;
7002 promoSweep = EmptySquare; // terminate sweep
7003 promoDefaultAltered = TRUE;
7004 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7007 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7008 if(clickType == Release) return; // ignore upclick of click-click destination
7009 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7010 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7011 if(gameInfo.holdingsWidth &&
7012 (WhiteOnMove(currentMove)
7013 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7014 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7015 // click in right holdings, for determining promotion piece
7016 ChessSquare p = boards[currentMove][y][x];
7017 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7018 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7019 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7020 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7025 DrawPosition(FALSE, boards[currentMove]);
7029 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7030 if(clickType == Press
7031 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7032 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7033 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7036 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7037 // could be static click on premove from-square: abort premove
7039 ClearPremoveHighlights();
7042 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7043 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7045 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7046 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7047 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7048 defaultPromoChoice = DefaultPromoChoice(side);
7051 autoQueen = appData.alwaysPromoteToQueen;
7055 gatingPiece = EmptySquare;
7056 if (clickType != Press) {
7057 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7058 DragPieceEnd(xPix, yPix); dragging = 0;
7059 DrawPosition(FALSE, NULL);
7063 doubleClick = FALSE;
7064 fromX = x; fromY = y; toX = toY = -1;
7065 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7066 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7067 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7069 if (OKToStartUserMove(fromX, fromY)) {
7071 MarkTargetSquares(0);
7072 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7073 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7074 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7075 promoSweep = defaultPromoChoice;
7076 selectFlag = 0; lastX = xPix; lastY = yPix;
7077 Sweep(0); // Pawn that is going to promote: preview promotion piece
7078 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7080 if (appData.highlightDragging) {
7081 SetHighlights(fromX, fromY, -1, -1);
7085 } else fromX = fromY = -1;
7091 if (clickType == Press && gameMode != EditPosition) {
7096 // ignore off-board to clicks
7097 if(y < 0 || x < 0) return;
7099 /* Check if clicking again on the same color piece */
7100 fromP = boards[currentMove][fromY][fromX];
7101 toP = boards[currentMove][y][x];
7102 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7103 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7104 WhitePawn <= toP && toP <= WhiteKing &&
7105 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7106 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7107 (BlackPawn <= fromP && fromP <= BlackKing &&
7108 BlackPawn <= toP && toP <= BlackKing &&
7109 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7110 !(fromP == BlackKing && toP == BlackRook && frc))) {
7111 /* Clicked again on same color piece -- changed his mind */
7112 second = (x == fromX && y == fromY);
7113 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7114 second = FALSE; // first double-click rather than scond click
7115 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7117 promoDefaultAltered = FALSE;
7118 MarkTargetSquares(1);
7119 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7120 if (appData.highlightDragging) {
7121 SetHighlights(x, y, -1, -1);
7125 if (OKToStartUserMove(x, y)) {
7126 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7127 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7128 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7129 gatingPiece = boards[currentMove][fromY][fromX];
7130 else gatingPiece = doubleClick ? fromP : EmptySquare;
7132 fromY = y; dragging = 1;
7133 MarkTargetSquares(0);
7134 DragPieceBegin(xPix, yPix, FALSE);
7135 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7136 promoSweep = defaultPromoChoice;
7137 selectFlag = 0; lastX = xPix; lastY = yPix;
7138 Sweep(0); // Pawn that is going to promote: preview promotion piece
7142 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7145 // ignore clicks on holdings
7146 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7149 if (clickType == Release && x == fromX && y == fromY) {
7150 DragPieceEnd(xPix, yPix); dragging = 0;
7152 // a deferred attempt to click-click move an empty square on top of a piece
7153 boards[currentMove][y][x] = EmptySquare;
7155 DrawPosition(FALSE, boards[currentMove]);
7156 fromX = fromY = -1; clearFlag = 0;
7159 if (appData.animateDragging) {
7160 /* Undo animation damage if any */
7161 DrawPosition(FALSE, NULL);
7163 if (second || sweepSelecting) {
7164 /* Second up/down in same square; just abort move */
7165 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7166 second = sweepSelecting = 0;
7168 gatingPiece = EmptySquare;
7171 ClearPremoveHighlights();
7173 /* First upclick in same square; start click-click mode */
7174 SetHighlights(x, y, -1, -1);
7181 /* we now have a different from- and (possibly off-board) to-square */
7182 /* Completed move */
7183 if(!sweepSelecting) {
7186 saveAnimate = appData.animate;
7187 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7189 if (clickType == Press) {
7190 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7191 // must be Edit Position mode with empty-square selected
7192 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7193 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7196 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7197 if(appData.sweepSelect) {
7198 ChessSquare piece = boards[currentMove][fromY][fromX];
7199 promoSweep = defaultPromoChoice;
7200 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7201 selectFlag = 0; lastX = xPix; lastY = yPix;
7202 Sweep(0); // Pawn that is going to promote: preview promotion piece
7204 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7205 MarkTargetSquares(1);
7207 return; // promo popup appears on up-click
7209 /* Finish clickclick move */
7210 if (appData.animate || appData.highlightLastMove) {
7211 SetHighlights(fromX, fromY, toX, toY);
7216 /* Finish drag move */
7217 if (appData.highlightLastMove) {
7218 SetHighlights(fromX, fromY, toX, toY);
7222 DragPieceEnd(xPix, yPix); dragging = 0;
7223 /* Don't animate move and drag both */
7224 appData.animate = FALSE;
7226 MarkTargetSquares(1);
7228 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7229 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7230 ChessSquare piece = boards[currentMove][fromY][fromX];
7231 if(gameMode == EditPosition && piece != EmptySquare &&
7232 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7235 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7236 n = PieceToNumber(piece - (int)BlackPawn);
7237 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7238 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7239 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7241 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7242 n = PieceToNumber(piece);
7243 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7244 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7245 boards[currentMove][n][BOARD_WIDTH-2]++;
7247 boards[currentMove][fromY][fromX] = EmptySquare;
7251 DrawPosition(TRUE, boards[currentMove]);
7255 // off-board moves should not be highlighted
7256 if(x < 0 || y < 0) ClearHighlights();
7258 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7260 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7261 SetHighlights(fromX, fromY, toX, toY);
7262 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7263 // [HGM] super: promotion to captured piece selected from holdings
7264 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7265 promotionChoice = TRUE;
7266 // kludge follows to temporarily execute move on display, without promoting yet
7267 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7268 boards[currentMove][toY][toX] = p;
7269 DrawPosition(FALSE, boards[currentMove]);
7270 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7271 boards[currentMove][toY][toX] = q;
7272 DisplayMessage("Click in holdings to choose piece", "");
7277 int oldMove = currentMove;
7278 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7279 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7280 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7281 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7282 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7283 DrawPosition(TRUE, boards[currentMove]);
7286 appData.animate = saveAnimate;
7287 if (appData.animate || appData.animateDragging) {
7288 /* Undo animation damage if needed */
7289 DrawPosition(FALSE, NULL);
7294 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7295 { // front-end-free part taken out of PieceMenuPopup
7296 int whichMenu; int xSqr, ySqr;
7298 if(seekGraphUp) { // [HGM] seekgraph
7299 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7300 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7304 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7305 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7306 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7307 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7308 if(action == Press) {
7309 originalFlip = flipView;
7310 flipView = !flipView; // temporarily flip board to see game from partners perspective
7311 DrawPosition(TRUE, partnerBoard);
7312 DisplayMessage(partnerStatus, "");
7314 } else if(action == Release) {
7315 flipView = originalFlip;
7316 DrawPosition(TRUE, boards[currentMove]);
7322 xSqr = EventToSquare(x, BOARD_WIDTH);
7323 ySqr = EventToSquare(y, BOARD_HEIGHT);
7324 if (action == Release) {
7325 if(pieceSweep != EmptySquare) {
7326 EditPositionMenuEvent(pieceSweep, toX, toY);
7327 pieceSweep = EmptySquare;
7328 } else UnLoadPV(); // [HGM] pv
7330 if (action != Press) return -2; // return code to be ignored
7333 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7335 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7336 if (xSqr < 0 || ySqr < 0) return -1;
7337 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7338 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7339 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7340 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7344 if(!appData.icsEngineAnalyze) return -1;
7345 case IcsPlayingWhite:
7346 case IcsPlayingBlack:
7347 if(!appData.zippyPlay) goto noZip;
7350 case MachinePlaysWhite:
7351 case MachinePlaysBlack:
7352 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7353 if (!appData.dropMenu) {
7355 return 2; // flag front-end to grab mouse events
7357 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7358 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7361 if (xSqr < 0 || ySqr < 0) return -1;
7362 if (!appData.dropMenu || appData.testLegality &&
7363 gameInfo.variant != VariantBughouse &&
7364 gameInfo.variant != VariantCrazyhouse) return -1;
7365 whichMenu = 1; // drop menu
7371 if (((*fromX = xSqr) < 0) ||
7372 ((*fromY = ySqr) < 0)) {
7373 *fromX = *fromY = -1;
7377 *fromX = BOARD_WIDTH - 1 - *fromX;
7379 *fromY = BOARD_HEIGHT - 1 - *fromY;
7385 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7387 // char * hint = lastHint;
7388 FrontEndProgramStats stats;
7390 stats.which = cps == &first ? 0 : 1;
7391 stats.depth = cpstats->depth;
7392 stats.nodes = cpstats->nodes;
7393 stats.score = cpstats->score;
7394 stats.time = cpstats->time;
7395 stats.pv = cpstats->movelist;
7396 stats.hint = lastHint;
7397 stats.an_move_index = 0;
7398 stats.an_move_count = 0;
7400 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7401 stats.hint = cpstats->move_name;
7402 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7403 stats.an_move_count = cpstats->nr_moves;
7406 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
7408 SetProgramStats( &stats );
7412 ClearEngineOutputPane (int which)
7414 static FrontEndProgramStats dummyStats;
7415 dummyStats.which = which;
7416 dummyStats.pv = "#";
7417 SetProgramStats( &dummyStats );
7420 #define MAXPLAYERS 500
7423 TourneyStandings (int display)
7425 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7426 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7427 char result, *p, *names[MAXPLAYERS];
7429 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7430 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7431 names[0] = p = strdup(appData.participants);
7432 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7434 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7436 while(result = appData.results[nr]) {
7437 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7438 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7439 wScore = bScore = 0;
7441 case '+': wScore = 2; break;
7442 case '-': bScore = 2; break;
7443 case '=': wScore = bScore = 1; break;
7445 case '*': return strdup("busy"); // tourney not finished
7453 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7454 for(w=0; w<nPlayers; w++) {
7456 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7457 ranking[w] = b; points[w] = bScore; score[b] = -2;
7459 p = malloc(nPlayers*34+1);
7460 for(w=0; w<nPlayers && w<display; w++)
7461 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7467 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7468 { // count all piece types
7470 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7471 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7472 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7475 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7476 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7477 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7478 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7479 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7480 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7485 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7487 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7488 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7490 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7491 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7492 if(myPawns == 2 && nMine == 3) // KPP
7493 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7494 if(myPawns == 1 && nMine == 2) // KP
7495 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7496 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7497 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7498 if(myPawns) return FALSE;
7499 if(pCnt[WhiteRook+side])
7500 return pCnt[BlackRook-side] ||
7501 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7502 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7503 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7504 if(pCnt[WhiteCannon+side]) {
7505 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7506 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7508 if(pCnt[WhiteKnight+side])
7509 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7514 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7516 VariantClass v = gameInfo.variant;
7518 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7519 if(v == VariantShatranj) return TRUE; // always winnable through baring
7520 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7521 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7523 if(v == VariantXiangqi) {
7524 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7526 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7527 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7528 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7529 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7530 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7531 if(stale) // we have at least one last-rank P plus perhaps C
7532 return majors // KPKX
7533 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7535 return pCnt[WhiteFerz+side] // KCAK
7536 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7537 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7538 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7540 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7541 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7543 if(nMine == 1) return FALSE; // bare King
7544 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
7545 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7546 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7547 // by now we have King + 1 piece (or multiple Bishops on the same color)
7548 if(pCnt[WhiteKnight+side])
7549 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7550 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7551 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7553 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7554 if(pCnt[WhiteAlfil+side])
7555 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7556 if(pCnt[WhiteWazir+side])
7557 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7564 CompareWithRights (Board b1, Board b2)
7567 if(!CompareBoards(b1, b2)) return FALSE;
7568 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7569 /* compare castling rights */
7570 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7571 rights++; /* King lost rights, while rook still had them */
7572 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7573 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7574 rights++; /* but at least one rook lost them */
7576 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7578 if( b1[CASTLING][5] != NoRights ) {
7579 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7586 Adjudicate (ChessProgramState *cps)
7587 { // [HGM] some adjudications useful with buggy engines
7588 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7589 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7590 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7591 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7592 int k, count = 0; static int bare = 1;
7593 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7594 Boolean canAdjudicate = !appData.icsActive;
7596 // most tests only when we understand the game, i.e. legality-checking on
7597 if( appData.testLegality )
7598 { /* [HGM] Some more adjudications for obstinate engines */
7599 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7600 static int moveCount = 6;
7602 char *reason = NULL;
7604 /* Count what is on board. */
7605 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7607 /* Some material-based adjudications that have to be made before stalemate test */
7608 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7609 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7610 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7611 if(canAdjudicate && appData.checkMates) {
7613 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7614 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7615 "Xboard adjudication: King destroyed", GE_XBOARD );
7620 /* Bare King in Shatranj (loses) or Losers (wins) */
7621 if( nrW == 1 || nrB == 1) {
7622 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7623 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7624 if(canAdjudicate && appData.checkMates) {
7626 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7627 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7628 "Xboard adjudication: Bare king", GE_XBOARD );
7632 if( gameInfo.variant == VariantShatranj && --bare < 0)
7634 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7635 if(canAdjudicate && appData.checkMates) {
7636 /* but only adjudicate if adjudication enabled */
7638 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7639 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7640 "Xboard adjudication: Bare king", GE_XBOARD );
7647 // don't wait for engine to announce game end if we can judge ourselves
7648 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7650 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7651 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7652 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7653 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7656 reason = "Xboard adjudication: 3rd check";
7657 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7667 reason = "Xboard adjudication: Stalemate";
7668 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7669 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7670 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7671 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7672 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7673 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7674 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7675 EP_CHECKMATE : EP_WINS);
7676 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7677 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7681 reason = "Xboard adjudication: Checkmate";
7682 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7686 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7688 result = GameIsDrawn; break;
7690 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7692 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7696 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7698 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699 GameEnds( result, reason, GE_XBOARD );
7703 /* Next absolutely insufficient mating material. */
7704 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7705 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7706 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7708 /* always flag draws, for judging claims */
7709 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7711 if(canAdjudicate && appData.materialDraws) {
7712 /* but only adjudicate them if adjudication enabled */
7713 if(engineOpponent) {
7714 SendToProgram("force\n", engineOpponent); // suppress reply
7715 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7717 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7722 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7723 if(gameInfo.variant == VariantXiangqi ?
7724 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7726 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7727 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7728 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7729 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7731 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7732 { /* if the first 3 moves do not show a tactical win, declare draw */
7733 if(engineOpponent) {
7734 SendToProgram("force\n", engineOpponent); // suppress reply
7735 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7737 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7740 } else moveCount = 6;
7743 // Repetition draws and 50-move rule can be applied independently of legality testing
7745 /* Check for rep-draws */
7747 for(k = forwardMostMove-2;
7748 k>=backwardMostMove && k>=forwardMostMove-100 &&
7749 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7750 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7753 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7754 /* compare castling rights */
7755 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7756 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7757 rights++; /* King lost rights, while rook still had them */
7758 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7759 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7760 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7761 rights++; /* but at least one rook lost them */
7763 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7764 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7766 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7767 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7768 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7771 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7772 && appData.drawRepeats > 1) {
7773 /* adjudicate after user-specified nr of repeats */
7774 int result = GameIsDrawn;
7775 char *details = "XBoard adjudication: repetition draw";
7776 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7777 // [HGM] xiangqi: check for forbidden perpetuals
7778 int m, ourPerpetual = 1, hisPerpetual = 1;
7779 for(m=forwardMostMove; m>k; m-=2) {
7780 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7781 ourPerpetual = 0; // the current mover did not always check
7782 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7783 hisPerpetual = 0; // the opponent did not always check
7785 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7786 ourPerpetual, hisPerpetual);
7787 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7788 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7789 details = "Xboard adjudication: perpetual checking";
7791 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7792 break; // (or we would have caught him before). Abort repetition-checking loop.
7794 // Now check for perpetual chases
7795 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7796 hisPerpetual = PerpetualChase(k, forwardMostMove);
7797 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7798 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7799 static char resdet[MSG_SIZ];
7800 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7802 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7804 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7805 break; // Abort repetition-checking loop.
7807 // if neither of us is checking or chasing all the time, or both are, it is draw
7809 if(engineOpponent) {
7810 SendToProgram("force\n", engineOpponent); // suppress reply
7811 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7813 GameEnds( result, details, GE_XBOARD );
7816 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7817 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7821 /* Now we test for 50-move draws. Determine ply count */
7822 count = forwardMostMove;
7823 /* look for last irreversble move */
7824 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7826 /* if we hit starting position, add initial plies */
7827 if( count == backwardMostMove )
7828 count -= initialRulePlies;
7829 count = forwardMostMove - count;
7830 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7831 // adjust reversible move counter for checks in Xiangqi
7832 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7833 if(i < backwardMostMove) i = backwardMostMove;
7834 while(i <= forwardMostMove) {
7835 lastCheck = inCheck; // check evasion does not count
7836 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7837 if(inCheck || lastCheck) count--; // check does not count
7842 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7843 /* this is used to judge if draw claims are legal */
7844 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7845 if(engineOpponent) {
7846 SendToProgram("force\n", engineOpponent); // suppress reply
7847 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7849 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7853 /* if draw offer is pending, treat it as a draw claim
7854 * when draw condition present, to allow engines a way to
7855 * claim draws before making their move to avoid a race
7856 * condition occurring after their move
7858 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7860 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7861 p = "Draw claim: 50-move rule";
7862 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7863 p = "Draw claim: 3-fold repetition";
7864 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7865 p = "Draw claim: insufficient mating material";
7866 if( p != NULL && canAdjudicate) {
7867 if(engineOpponent) {
7868 SendToProgram("force\n", engineOpponent); // suppress reply
7869 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7871 GameEnds( GameIsDrawn, p, GE_XBOARD );
7876 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7877 if(engineOpponent) {
7878 SendToProgram("force\n", engineOpponent); // suppress reply
7879 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7881 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7888 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7889 { // [HGM] book: this routine intercepts moves to simulate book replies
7890 char *bookHit = NULL;
7892 //first determine if the incoming move brings opponent into his book
7893 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7894 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7895 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7896 if(bookHit != NULL && !cps->bookSuspend) {
7897 // make sure opponent is not going to reply after receiving move to book position
7898 SendToProgram("force\n", cps);
7899 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7901 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7902 // now arrange restart after book miss
7904 // after a book hit we never send 'go', and the code after the call to this routine
7905 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7906 char buf[MSG_SIZ], *move = bookHit;
7908 int fromX, fromY, toX, toY;
7912 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7913 &fromX, &fromY, &toX, &toY, &promoChar)) {
7914 (void) CoordsToAlgebraic(boards[forwardMostMove],
7915 PosFlags(forwardMostMove),
7916 fromY, fromX, toY, toX, promoChar, move);
7918 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7922 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7923 SendToProgram(buf, cps);
7924 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7925 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7926 SendToProgram("go\n", cps);
7927 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7928 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7929 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7930 SendToProgram("go\n", cps);
7931 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7933 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7937 LoadError (char *errmess, ChessProgramState *cps)
7938 { // unloads engine and switches back to -ncp mode if it was first
7939 if(cps->initDone) return FALSE;
7940 cps->isr = NULL; // this should suppress further error popups from breaking pipes
7941 DestroyChildProcess(cps->pr, 9 ); // just to be sure
7944 appData.noChessProgram = TRUE;
7945 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7946 gameMode = BeginningOfGame; ModeHighlight();
7949 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7950 DisplayMessage("", ""); // erase waiting message
7951 if(errmess) DisplayError(errmess, 0); // announce reason, if given
7956 ChessProgramState *savedState;
7958 DeferredBookMove (void)
7960 if(savedState->lastPing != savedState->lastPong)
7961 ScheduleDelayedEvent(DeferredBookMove, 10);
7963 HandleMachineMove(savedMessage, savedState);
7966 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7969 HandleMachineMove (char *message, ChessProgramState *cps)
7971 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7972 char realname[MSG_SIZ];
7973 int fromX, fromY, toX, toY;
7977 int machineWhite, oldError;
7980 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7981 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7982 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7983 DisplayError(_("Invalid pairing from pairing engine"), 0);
7986 pairingReceived = 1;
7988 return; // Skim the pairing messages here.
7991 oldError = cps->userError; cps->userError = 0;
7993 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7995 * Kludge to ignore BEL characters
7997 while (*message == '\007') message++;
8000 * [HGM] engine debug message: ignore lines starting with '#' character
8002 if(cps->debug && *message == '#') return;
8005 * Look for book output
8007 if (cps == &first && bookRequested) {
8008 if (message[0] == '\t' || message[0] == ' ') {
8009 /* Part of the book output is here; append it */
8010 strcat(bookOutput, message);
8011 strcat(bookOutput, " \n");
8013 } else if (bookOutput[0] != NULLCHAR) {
8014 /* All of book output has arrived; display it */
8015 char *p = bookOutput;
8016 while (*p != NULLCHAR) {
8017 if (*p == '\t') *p = ' ';
8020 DisplayInformation(bookOutput);
8021 bookRequested = FALSE;
8022 /* Fall through to parse the current output */
8027 * Look for machine move.
8029 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8030 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8032 /* This method is only useful on engines that support ping */
8033 if (cps->lastPing != cps->lastPong) {
8034 if (gameMode == BeginningOfGame) {
8035 /* Extra move from before last new; ignore */
8036 if (appData.debugMode) {
8037 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8040 if (appData.debugMode) {
8041 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8042 cps->which, gameMode);
8045 SendToProgram("undo\n", cps);
8051 case BeginningOfGame:
8052 /* Extra move from before last reset; ignore */
8053 if (appData.debugMode) {
8054 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8061 /* Extra move after we tried to stop. The mode test is
8062 not a reliable way of detecting this problem, but it's
8063 the best we can do on engines that don't support ping.
8065 if (appData.debugMode) {
8066 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8067 cps->which, gameMode);
8069 SendToProgram("undo\n", cps);
8072 case MachinePlaysWhite:
8073 case IcsPlayingWhite:
8074 machineWhite = TRUE;
8077 case MachinePlaysBlack:
8078 case IcsPlayingBlack:
8079 machineWhite = FALSE;
8082 case TwoMachinesPlay:
8083 machineWhite = (cps->twoMachinesColor[0] == 'w');
8086 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8087 if (appData.debugMode) {
8089 "Ignoring move out of turn by %s, gameMode %d"
8090 ", forwardMost %d\n",
8091 cps->which, gameMode, forwardMostMove);
8096 if(cps->alphaRank) AlphaRank(machineMove, 4);
8097 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8098 &fromX, &fromY, &toX, &toY, &promoChar)) {
8099 /* Machine move could not be parsed; ignore it. */
8100 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8101 machineMove, _(cps->which));
8102 DisplayError(buf1, 0);
8103 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8104 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8105 if (gameMode == TwoMachinesPlay) {
8106 GameEnds(machineWhite ? BlackWins : WhiteWins,
8112 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8113 /* So we have to redo legality test with true e.p. status here, */
8114 /* to make sure an illegal e.p. capture does not slip through, */
8115 /* to cause a forfeit on a justified illegal-move complaint */
8116 /* of the opponent. */
8117 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8119 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8120 fromY, fromX, toY, toX, promoChar);
8121 if(moveType == IllegalMove) {
8122 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8123 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8124 GameEnds(machineWhite ? BlackWins : WhiteWins,
8127 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8128 /* [HGM] Kludge to handle engines that send FRC-style castling
8129 when they shouldn't (like TSCP-Gothic) */
8131 case WhiteASideCastleFR:
8132 case BlackASideCastleFR:
8134 currentMoveString[2]++;
8136 case WhiteHSideCastleFR:
8137 case BlackHSideCastleFR:
8139 currentMoveString[2]--;
8141 default: ; // nothing to do, but suppresses warning of pedantic compilers
8144 hintRequested = FALSE;
8145 lastHint[0] = NULLCHAR;
8146 bookRequested = FALSE;
8147 /* Program may be pondering now */
8148 cps->maybeThinking = TRUE;
8149 if (cps->sendTime == 2) cps->sendTime = 1;
8150 if (cps->offeredDraw) cps->offeredDraw--;
8152 /* [AS] Save move info*/
8153 pvInfoList[ forwardMostMove ].score = programStats.score;
8154 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8155 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8157 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8159 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8160 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8163 while( count < adjudicateLossPlies ) {
8164 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8167 score = -score; /* Flip score for winning side */
8170 if( score > adjudicateLossThreshold ) {
8177 if( count >= adjudicateLossPlies ) {
8178 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8180 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8181 "Xboard adjudication",
8188 if(Adjudicate(cps)) {
8189 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8190 return; // [HGM] adjudicate: for all automatic game ends
8194 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8196 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8197 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8199 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8201 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8203 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8204 char buf[3*MSG_SIZ];
8206 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8207 programStats.score / 100.,
8209 programStats.time / 100.,
8210 (unsigned int)programStats.nodes,
8211 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8212 programStats.movelist);
8214 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8219 /* [AS] Clear stats for next move */
8220 ClearProgramStats();
8221 thinkOutput[0] = NULLCHAR;
8222 hiddenThinkOutputState = 0;
8225 if (gameMode == TwoMachinesPlay) {
8226 /* [HGM] relaying draw offers moved to after reception of move */
8227 /* and interpreting offer as claim if it brings draw condition */
8228 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8229 SendToProgram("draw\n", cps->other);
8231 if (cps->other->sendTime) {
8232 SendTimeRemaining(cps->other,
8233 cps->other->twoMachinesColor[0] == 'w');
8235 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8236 if (firstMove && !bookHit) {
8238 if (cps->other->useColors) {
8239 SendToProgram(cps->other->twoMachinesColor, cps->other);
8241 SendToProgram("go\n", cps->other);
8243 cps->other->maybeThinking = TRUE;
8246 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8248 if (!pausing && appData.ringBellAfterMoves) {
8253 * Reenable menu items that were disabled while
8254 * machine was thinking
8256 if (gameMode != TwoMachinesPlay)
8257 SetUserThinkingEnables();
8259 // [HGM] book: after book hit opponent has received move and is now in force mode
8260 // force the book reply into it, and then fake that it outputted this move by jumping
8261 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8263 static char bookMove[MSG_SIZ]; // a bit generous?
8265 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8266 strcat(bookMove, bookHit);
8269 programStats.nodes = programStats.depth = programStats.time =
8270 programStats.score = programStats.got_only_move = 0;
8271 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8273 if(cps->lastPing != cps->lastPong) {
8274 savedMessage = message; // args for deferred call
8276 ScheduleDelayedEvent(DeferredBookMove, 10);
8285 /* Set special modes for chess engines. Later something general
8286 * could be added here; for now there is just one kludge feature,
8287 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8288 * when "xboard" is given as an interactive command.
8290 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8291 cps->useSigint = FALSE;
8292 cps->useSigterm = FALSE;
8294 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8295 ParseFeatures(message+8, cps);
8296 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8299 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8300 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8301 int dummy, s=6; char buf[MSG_SIZ];
8302 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8303 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8304 if(startedFromSetupPosition) return;
8305 ParseFEN(boards[0], &dummy, message+s);
8306 DrawPosition(TRUE, boards[0]);
8307 startedFromSetupPosition = TRUE;
8310 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8311 * want this, I was asked to put it in, and obliged.
8313 if (!strncmp(message, "setboard ", 9)) {
8314 Board initial_position;
8316 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8318 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8319 DisplayError(_("Bad FEN received from engine"), 0);
8323 CopyBoard(boards[0], initial_position);
8324 initialRulePlies = FENrulePlies;
8325 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8326 else gameMode = MachinePlaysBlack;
8327 DrawPosition(FALSE, boards[currentMove]);
8333 * Look for communication commands
8335 if (!strncmp(message, "telluser ", 9)) {
8336 if(message[9] == '\\' && message[10] == '\\')
8337 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8339 DisplayNote(message + 9);
8342 if (!strncmp(message, "tellusererror ", 14)) {
8344 if(message[14] == '\\' && message[15] == '\\')
8345 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8347 DisplayError(message + 14, 0);
8350 if (!strncmp(message, "tellopponent ", 13)) {
8351 if (appData.icsActive) {
8353 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8357 DisplayNote(message + 13);
8361 if (!strncmp(message, "tellothers ", 11)) {
8362 if (appData.icsActive) {
8364 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8370 if (!strncmp(message, "tellall ", 8)) {
8371 if (appData.icsActive) {
8373 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8377 DisplayNote(message + 8);
8381 if (strncmp(message, "warning", 7) == 0) {
8382 /* Undocumented feature, use tellusererror in new code */
8383 DisplayError(message, 0);
8386 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8387 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8388 strcat(realname, " query");
8389 AskQuestion(realname, buf2, buf1, cps->pr);
8392 /* Commands from the engine directly to ICS. We don't allow these to be
8393 * sent until we are logged on. Crafty kibitzes have been known to
8394 * interfere with the login process.
8397 if (!strncmp(message, "tellics ", 8)) {
8398 SendToICS(message + 8);
8402 if (!strncmp(message, "tellicsnoalias ", 15)) {
8403 SendToICS(ics_prefix);
8404 SendToICS(message + 15);
8408 /* The following are for backward compatibility only */
8409 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8410 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8411 SendToICS(ics_prefix);
8417 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8421 * If the move is illegal, cancel it and redraw the board.
8422 * Also deal with other error cases. Matching is rather loose
8423 * here to accommodate engines written before the spec.
8425 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8426 strncmp(message, "Error", 5) == 0) {
8427 if (StrStr(message, "name") ||
8428 StrStr(message, "rating") || StrStr(message, "?") ||
8429 StrStr(message, "result") || StrStr(message, "board") ||
8430 StrStr(message, "bk") || StrStr(message, "computer") ||
8431 StrStr(message, "variant") || StrStr(message, "hint") ||
8432 StrStr(message, "random") || StrStr(message, "depth") ||
8433 StrStr(message, "accepted")) {
8436 if (StrStr(message, "protover")) {
8437 /* Program is responding to input, so it's apparently done
8438 initializing, and this error message indicates it is
8439 protocol version 1. So we don't need to wait any longer
8440 for it to initialize and send feature commands. */
8441 FeatureDone(cps, 1);
8442 cps->protocolVersion = 1;
8445 cps->maybeThinking = FALSE;
8447 if (StrStr(message, "draw")) {
8448 /* Program doesn't have "draw" command */
8449 cps->sendDrawOffers = 0;
8452 if (cps->sendTime != 1 &&
8453 (StrStr(message, "time") || StrStr(message, "otim"))) {
8454 /* Program apparently doesn't have "time" or "otim" command */
8458 if (StrStr(message, "analyze")) {
8459 cps->analysisSupport = FALSE;
8460 cps->analyzing = FALSE;
8461 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8462 EditGameEvent(); // [HGM] try to preserve loaded game
8463 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8464 DisplayError(buf2, 0);
8467 if (StrStr(message, "(no matching move)st")) {
8468 /* Special kludge for GNU Chess 4 only */
8469 cps->stKludge = TRUE;
8470 SendTimeControl(cps, movesPerSession, timeControl,
8471 timeIncrement, appData.searchDepth,
8475 if (StrStr(message, "(no matching move)sd")) {
8476 /* Special kludge for GNU Chess 4 only */
8477 cps->sdKludge = TRUE;
8478 SendTimeControl(cps, movesPerSession, timeControl,
8479 timeIncrement, appData.searchDepth,
8483 if (!StrStr(message, "llegal")) {
8486 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8487 gameMode == IcsIdle) return;
8488 if (forwardMostMove <= backwardMostMove) return;
8489 if (pausing) PauseEvent();
8490 if(appData.forceIllegal) {
8491 // [HGM] illegal: machine refused move; force position after move into it
8492 SendToProgram("force\n", cps);
8493 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8494 // we have a real problem now, as SendBoard will use the a2a3 kludge
8495 // when black is to move, while there might be nothing on a2 or black
8496 // might already have the move. So send the board as if white has the move.
8497 // But first we must change the stm of the engine, as it refused the last move
8498 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8499 if(WhiteOnMove(forwardMostMove)) {
8500 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8501 SendBoard(cps, forwardMostMove); // kludgeless board
8503 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8504 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8505 SendBoard(cps, forwardMostMove+1); // kludgeless board
8507 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8508 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8509 gameMode == TwoMachinesPlay)
8510 SendToProgram("go\n", cps);
8513 if (gameMode == PlayFromGameFile) {
8514 /* Stop reading this game file */
8515 gameMode = EditGame;
8518 /* [HGM] illegal-move claim should forfeit game when Xboard */
8519 /* only passes fully legal moves */
8520 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8521 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8522 "False illegal-move claim", GE_XBOARD );
8523 return; // do not take back move we tested as valid
8525 currentMove = forwardMostMove-1;
8526 DisplayMove(currentMove-1); /* before DisplayMoveError */
8527 SwitchClocks(forwardMostMove-1); // [HGM] race
8528 DisplayBothClocks();
8529 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8530 parseList[currentMove], _(cps->which));
8531 DisplayMoveError(buf1);
8532 DrawPosition(FALSE, boards[currentMove]);
8534 SetUserThinkingEnables();
8537 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8538 /* Program has a broken "time" command that
8539 outputs a string not ending in newline.
8545 * If chess program startup fails, exit with an error message.
8546 * Attempts to recover here are futile. [HGM] Well, we try anyway
8548 if ((StrStr(message, "unknown host") != NULL)
8549 || (StrStr(message, "No remote directory") != NULL)
8550 || (StrStr(message, "not found") != NULL)
8551 || (StrStr(message, "No such file") != NULL)
8552 || (StrStr(message, "can't alloc") != NULL)
8553 || (StrStr(message, "Permission denied") != NULL)) {
8555 cps->maybeThinking = FALSE;
8556 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8557 _(cps->which), cps->program, cps->host, message);
8558 RemoveInputSource(cps->isr);
8559 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8560 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8561 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8567 * Look for hint output
8569 if (sscanf(message, "Hint: %s", buf1) == 1) {
8570 if (cps == &first && hintRequested) {
8571 hintRequested = FALSE;
8572 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8573 &fromX, &fromY, &toX, &toY, &promoChar)) {
8574 (void) CoordsToAlgebraic(boards[forwardMostMove],
8575 PosFlags(forwardMostMove),
8576 fromY, fromX, toY, toX, promoChar, buf1);
8577 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8578 DisplayInformation(buf2);
8580 /* Hint move could not be parsed!? */
8581 snprintf(buf2, sizeof(buf2),
8582 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8583 buf1, _(cps->which));
8584 DisplayError(buf2, 0);
8587 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8593 * Ignore other messages if game is not in progress
8595 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8596 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8599 * look for win, lose, draw, or draw offer
8601 if (strncmp(message, "1-0", 3) == 0) {
8602 char *p, *q, *r = "";
8603 p = strchr(message, '{');
8611 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8613 } else if (strncmp(message, "0-1", 3) == 0) {
8614 char *p, *q, *r = "";
8615 p = strchr(message, '{');
8623 /* Kludge for Arasan 4.1 bug */
8624 if (strcmp(r, "Black resigns") == 0) {
8625 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8628 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8630 } else if (strncmp(message, "1/2", 3) == 0) {
8631 char *p, *q, *r = "";
8632 p = strchr(message, '{');
8641 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8644 } else if (strncmp(message, "White resign", 12) == 0) {
8645 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8647 } else if (strncmp(message, "Black resign", 12) == 0) {
8648 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8650 } else if (strncmp(message, "White matches", 13) == 0 ||
8651 strncmp(message, "Black matches", 13) == 0 ) {
8652 /* [HGM] ignore GNUShogi noises */
8654 } else if (strncmp(message, "White", 5) == 0 &&
8655 message[5] != '(' &&
8656 StrStr(message, "Black") == NULL) {
8657 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8659 } else if (strncmp(message, "Black", 5) == 0 &&
8660 message[5] != '(') {
8661 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8663 } else if (strcmp(message, "resign") == 0 ||
8664 strcmp(message, "computer resigns") == 0) {
8666 case MachinePlaysBlack:
8667 case IcsPlayingBlack:
8668 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8670 case MachinePlaysWhite:
8671 case IcsPlayingWhite:
8672 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8674 case TwoMachinesPlay:
8675 if (cps->twoMachinesColor[0] == 'w')
8676 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8678 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8685 } else if (strncmp(message, "opponent mates", 14) == 0) {
8687 case MachinePlaysBlack:
8688 case IcsPlayingBlack:
8689 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8691 case MachinePlaysWhite:
8692 case IcsPlayingWhite:
8693 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8695 case TwoMachinesPlay:
8696 if (cps->twoMachinesColor[0] == 'w')
8697 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8699 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8706 } else if (strncmp(message, "computer mates", 14) == 0) {
8708 case MachinePlaysBlack:
8709 case IcsPlayingBlack:
8710 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8712 case MachinePlaysWhite:
8713 case IcsPlayingWhite:
8714 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8716 case TwoMachinesPlay:
8717 if (cps->twoMachinesColor[0] == 'w')
8718 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8720 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8727 } else if (strncmp(message, "checkmate", 9) == 0) {
8728 if (WhiteOnMove(forwardMostMove)) {
8729 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8731 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8734 } else if (strstr(message, "Draw") != NULL ||
8735 strstr(message, "game is a draw") != NULL) {
8736 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8738 } else if (strstr(message, "offer") != NULL &&
8739 strstr(message, "draw") != NULL) {
8741 if (appData.zippyPlay && first.initDone) {
8742 /* Relay offer to ICS */
8743 SendToICS(ics_prefix);
8744 SendToICS("draw\n");
8747 cps->offeredDraw = 2; /* valid until this engine moves twice */
8748 if (gameMode == TwoMachinesPlay) {
8749 if (cps->other->offeredDraw) {
8750 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8751 /* [HGM] in two-machine mode we delay relaying draw offer */
8752 /* until after we also have move, to see if it is really claim */
8754 } else if (gameMode == MachinePlaysWhite ||
8755 gameMode == MachinePlaysBlack) {
8756 if (userOfferedDraw) {
8757 DisplayInformation(_("Machine accepts your draw offer"));
8758 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8760 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8767 * Look for thinking output
8769 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8770 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8772 int plylev, mvleft, mvtot, curscore, time;
8773 char mvname[MOVE_LEN];
8777 int prefixHint = FALSE;
8778 mvname[0] = NULLCHAR;
8781 case MachinePlaysBlack:
8782 case IcsPlayingBlack:
8783 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8785 case MachinePlaysWhite:
8786 case IcsPlayingWhite:
8787 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8792 case IcsObserving: /* [DM] icsEngineAnalyze */
8793 if (!appData.icsEngineAnalyze) ignore = TRUE;
8795 case TwoMachinesPlay:
8796 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8806 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8808 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8809 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8811 if (plyext != ' ' && plyext != '\t') {
8815 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816 if( cps->scoreIsAbsolute &&
8817 ( gameMode == MachinePlaysBlack ||
8818 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8819 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8820 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8821 !WhiteOnMove(currentMove)
8824 curscore = -curscore;
8827 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8829 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8832 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8833 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8834 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8835 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8836 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8837 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8839 } else DisplayError(_("failed writing PV"), 0);
8842 tempStats.depth = plylev;
8843 tempStats.nodes = nodes;
8844 tempStats.time = time;
8845 tempStats.score = curscore;
8846 tempStats.got_only_move = 0;
8848 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8851 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8852 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8853 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8854 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8855 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8856 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8857 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8858 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8861 /* Buffer overflow protection */
8862 if (pv[0] != NULLCHAR) {
8863 if (strlen(pv) >= sizeof(tempStats.movelist)
8864 && appData.debugMode) {
8866 "PV is too long; using the first %u bytes.\n",
8867 (unsigned) sizeof(tempStats.movelist) - 1);
8870 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8872 sprintf(tempStats.movelist, " no PV\n");
8875 if (tempStats.seen_stat) {
8876 tempStats.ok_to_send = 1;
8879 if (strchr(tempStats.movelist, '(') != NULL) {
8880 tempStats.line_is_book = 1;
8881 tempStats.nr_moves = 0;
8882 tempStats.moves_left = 0;
8884 tempStats.line_is_book = 0;
8887 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8888 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8890 SendProgramStatsToFrontend( cps, &tempStats );
8893 [AS] Protect the thinkOutput buffer from overflow... this
8894 is only useful if buf1 hasn't overflowed first!
8896 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8898 (gameMode == TwoMachinesPlay ?
8899 ToUpper(cps->twoMachinesColor[0]) : ' '),
8900 ((double) curscore) / 100.0,
8901 prefixHint ? lastHint : "",
8902 prefixHint ? " " : "" );
8904 if( buf1[0] != NULLCHAR ) {
8905 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8907 if( strlen(pv) > max_len ) {
8908 if( appData.debugMode) {
8909 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8911 pv[max_len+1] = '\0';
8914 strcat( thinkOutput, pv);
8917 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8918 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8919 DisplayMove(currentMove - 1);
8923 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8924 /* crafty (9.25+) says "(only move) <move>"
8925 * if there is only 1 legal move
8927 sscanf(p, "(only move) %s", buf1);
8928 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8929 sprintf(programStats.movelist, "%s (only move)", buf1);
8930 programStats.depth = 1;
8931 programStats.nr_moves = 1;
8932 programStats.moves_left = 1;
8933 programStats.nodes = 1;
8934 programStats.time = 1;
8935 programStats.got_only_move = 1;
8937 /* Not really, but we also use this member to
8938 mean "line isn't going to change" (Crafty
8939 isn't searching, so stats won't change) */
8940 programStats.line_is_book = 1;
8942 SendProgramStatsToFrontend( cps, &programStats );
8944 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8945 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8946 DisplayMove(currentMove - 1);
8949 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8950 &time, &nodes, &plylev, &mvleft,
8951 &mvtot, mvname) >= 5) {
8952 /* The stat01: line is from Crafty (9.29+) in response
8953 to the "." command */
8954 programStats.seen_stat = 1;
8955 cps->maybeThinking = TRUE;
8957 if (programStats.got_only_move || !appData.periodicUpdates)
8960 programStats.depth = plylev;
8961 programStats.time = time;
8962 programStats.nodes = nodes;
8963 programStats.moves_left = mvleft;
8964 programStats.nr_moves = mvtot;
8965 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8966 programStats.ok_to_send = 1;
8967 programStats.movelist[0] = '\0';
8969 SendProgramStatsToFrontend( cps, &programStats );
8973 } else if (strncmp(message,"++",2) == 0) {
8974 /* Crafty 9.29+ outputs this */
8975 programStats.got_fail = 2;
8978 } else if (strncmp(message,"--",2) == 0) {
8979 /* Crafty 9.29+ outputs this */
8980 programStats.got_fail = 1;
8983 } else if (thinkOutput[0] != NULLCHAR &&
8984 strncmp(message, " ", 4) == 0) {
8985 unsigned message_len;
8988 while (*p && *p == ' ') p++;
8990 message_len = strlen( p );
8992 /* [AS] Avoid buffer overflow */
8993 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8994 strcat(thinkOutput, " ");
8995 strcat(thinkOutput, p);
8998 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8999 strcat(programStats.movelist, " ");
9000 strcat(programStats.movelist, p);
9003 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9004 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9005 DisplayMove(currentMove - 1);
9013 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9014 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9016 ChessProgramStats cpstats;
9018 if (plyext != ' ' && plyext != '\t') {
9022 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9023 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9024 curscore = -curscore;
9027 cpstats.depth = plylev;
9028 cpstats.nodes = nodes;
9029 cpstats.time = time;
9030 cpstats.score = curscore;
9031 cpstats.got_only_move = 0;
9032 cpstats.movelist[0] = '\0';
9034 if (buf1[0] != NULLCHAR) {
9035 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9038 cpstats.ok_to_send = 0;
9039 cpstats.line_is_book = 0;
9040 cpstats.nr_moves = 0;
9041 cpstats.moves_left = 0;
9043 SendProgramStatsToFrontend( cps, &cpstats );
9050 /* Parse a game score from the character string "game", and
9051 record it as the history of the current game. The game
9052 score is NOT assumed to start from the standard position.
9053 The display is not updated in any way.
9056 ParseGameHistory (char *game)
9059 int fromX, fromY, toX, toY, boardIndex;
9064 if (appData.debugMode)
9065 fprintf(debugFP, "Parsing game history: %s\n", game);
9067 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9068 gameInfo.site = StrSave(appData.icsHost);
9069 gameInfo.date = PGNDate();
9070 gameInfo.round = StrSave("-");
9072 /* Parse out names of players */
9073 while (*game == ' ') game++;
9075 while (*game != ' ') *p++ = *game++;
9077 gameInfo.white = StrSave(buf);
9078 while (*game == ' ') game++;
9080 while (*game != ' ' && *game != '\n') *p++ = *game++;
9082 gameInfo.black = StrSave(buf);
9085 boardIndex = blackPlaysFirst ? 1 : 0;
9088 yyboardindex = boardIndex;
9089 moveType = (ChessMove) Myylex();
9091 case IllegalMove: /* maybe suicide chess, etc. */
9092 if (appData.debugMode) {
9093 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9094 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9095 setbuf(debugFP, NULL);
9097 case WhitePromotion:
9098 case BlackPromotion:
9099 case WhiteNonPromotion:
9100 case BlackNonPromotion:
9102 case WhiteCapturesEnPassant:
9103 case BlackCapturesEnPassant:
9104 case WhiteKingSideCastle:
9105 case WhiteQueenSideCastle:
9106 case BlackKingSideCastle:
9107 case BlackQueenSideCastle:
9108 case WhiteKingSideCastleWild:
9109 case WhiteQueenSideCastleWild:
9110 case BlackKingSideCastleWild:
9111 case BlackQueenSideCastleWild:
9113 case WhiteHSideCastleFR:
9114 case WhiteASideCastleFR:
9115 case BlackHSideCastleFR:
9116 case BlackASideCastleFR:
9118 fromX = currentMoveString[0] - AAA;
9119 fromY = currentMoveString[1] - ONE;
9120 toX = currentMoveString[2] - AAA;
9121 toY = currentMoveString[3] - ONE;
9122 promoChar = currentMoveString[4];
9126 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9127 fromX = moveType == WhiteDrop ?
9128 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9129 (int) CharToPiece(ToLower(currentMoveString[0]));
9131 toX = currentMoveString[2] - AAA;
9132 toY = currentMoveString[3] - ONE;
9133 promoChar = NULLCHAR;
9137 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9138 if (appData.debugMode) {
9139 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9140 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9141 setbuf(debugFP, NULL);
9143 DisplayError(buf, 0);
9145 case ImpossibleMove:
9147 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9148 if (appData.debugMode) {
9149 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9150 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9151 setbuf(debugFP, NULL);
9153 DisplayError(buf, 0);
9156 if (boardIndex < backwardMostMove) {
9157 /* Oops, gap. How did that happen? */
9158 DisplayError(_("Gap in move list"), 0);
9161 backwardMostMove = blackPlaysFirst ? 1 : 0;
9162 if (boardIndex > forwardMostMove) {
9163 forwardMostMove = boardIndex;
9167 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9168 strcat(parseList[boardIndex-1], " ");
9169 strcat(parseList[boardIndex-1], yy_text);
9181 case GameUnfinished:
9182 if (gameMode == IcsExamining) {
9183 if (boardIndex < backwardMostMove) {
9184 /* Oops, gap. How did that happen? */
9187 backwardMostMove = blackPlaysFirst ? 1 : 0;
9190 gameInfo.result = moveType;
9191 p = strchr(yy_text, '{');
9192 if (p == NULL) p = strchr(yy_text, '(');
9195 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9197 q = strchr(p, *p == '{' ? '}' : ')');
9198 if (q != NULL) *q = NULLCHAR;
9201 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9202 gameInfo.resultDetails = StrSave(p);
9205 if (boardIndex >= forwardMostMove &&
9206 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9207 backwardMostMove = blackPlaysFirst ? 1 : 0;
9210 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9211 fromY, fromX, toY, toX, promoChar,
9212 parseList[boardIndex]);
9213 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9214 /* currentMoveString is set as a side-effect of yylex */
9215 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9216 strcat(moveList[boardIndex], "\n");
9218 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9219 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9225 if(gameInfo.variant != VariantShogi)
9226 strcat(parseList[boardIndex - 1], "+");
9230 strcat(parseList[boardIndex - 1], "#");
9237 /* Apply a move to the given board */
9239 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9241 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9242 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9244 /* [HGM] compute & store e.p. status and castling rights for new position */
9245 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9247 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9248 oldEP = (signed char)board[EP_STATUS];
9249 board[EP_STATUS] = EP_NONE;
9251 if (fromY == DROP_RANK) {
9253 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9254 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9257 piece = board[toY][toX] = (ChessSquare) fromX;
9261 if( board[toY][toX] != EmptySquare )
9262 board[EP_STATUS] = EP_CAPTURE;
9264 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9265 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9266 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9268 if( board[fromY][fromX] == WhitePawn ) {
9269 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9270 board[EP_STATUS] = EP_PAWN_MOVE;
9272 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9273 gameInfo.variant != VariantBerolina || toX < fromX)
9274 board[EP_STATUS] = toX | berolina;
9275 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9276 gameInfo.variant != VariantBerolina || toX > fromX)
9277 board[EP_STATUS] = toX;
9280 if( board[fromY][fromX] == BlackPawn ) {
9281 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9282 board[EP_STATUS] = EP_PAWN_MOVE;
9283 if( toY-fromY== -2) {
9284 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9285 gameInfo.variant != VariantBerolina || toX < fromX)
9286 board[EP_STATUS] = toX | berolina;
9287 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9288 gameInfo.variant != VariantBerolina || toX > fromX)
9289 board[EP_STATUS] = toX;
9293 for(i=0; i<nrCastlingRights; i++) {
9294 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9295 board[CASTLING][i] == toX && castlingRank[i] == toY
9296 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9299 if (fromX == toX && fromY == toY) return;
9301 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9302 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9303 if(gameInfo.variant == VariantKnightmate)
9304 king += (int) WhiteUnicorn - (int) WhiteKing;
9306 /* Code added by Tord: */
9307 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9308 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9309 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9310 board[fromY][fromX] = EmptySquare;
9311 board[toY][toX] = EmptySquare;
9312 if((toX > fromX) != (piece == WhiteRook)) {
9313 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9315 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9317 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9318 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9319 board[fromY][fromX] = EmptySquare;
9320 board[toY][toX] = EmptySquare;
9321 if((toX > fromX) != (piece == BlackRook)) {
9322 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9324 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9326 /* End of code added by Tord */
9328 } else if (board[fromY][fromX] == king
9329 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9330 && toY == fromY && toX > fromX+1) {
9331 board[fromY][fromX] = EmptySquare;
9332 board[toY][toX] = king;
9333 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9334 board[fromY][BOARD_RGHT-1] = EmptySquare;
9335 } else if (board[fromY][fromX] == king
9336 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9337 && toY == fromY && toX < fromX-1) {
9338 board[fromY][fromX] = EmptySquare;
9339 board[toY][toX] = king;
9340 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9341 board[fromY][BOARD_LEFT] = EmptySquare;
9342 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9343 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9344 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9346 /* white pawn promotion */
9347 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9348 if(gameInfo.variant==VariantBughouse ||
9349 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9350 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9351 board[fromY][fromX] = EmptySquare;
9352 } else if ((fromY >= BOARD_HEIGHT>>1)
9354 && gameInfo.variant != VariantXiangqi
9355 && gameInfo.variant != VariantBerolina
9356 && (board[fromY][fromX] == WhitePawn)
9357 && (board[toY][toX] == EmptySquare)) {
9358 board[fromY][fromX] = EmptySquare;
9359 board[toY][toX] = WhitePawn;
9360 captured = board[toY - 1][toX];
9361 board[toY - 1][toX] = EmptySquare;
9362 } else if ((fromY == BOARD_HEIGHT-4)
9364 && gameInfo.variant == VariantBerolina
9365 && (board[fromY][fromX] == WhitePawn)
9366 && (board[toY][toX] == EmptySquare)) {
9367 board[fromY][fromX] = EmptySquare;
9368 board[toY][toX] = WhitePawn;
9369 if(oldEP & EP_BEROLIN_A) {
9370 captured = board[fromY][fromX-1];
9371 board[fromY][fromX-1] = EmptySquare;
9372 }else{ captured = board[fromY][fromX+1];
9373 board[fromY][fromX+1] = EmptySquare;
9375 } else if (board[fromY][fromX] == king
9376 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9377 && toY == fromY && toX > fromX+1) {
9378 board[fromY][fromX] = EmptySquare;
9379 board[toY][toX] = king;
9380 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9381 board[fromY][BOARD_RGHT-1] = EmptySquare;
9382 } else if (board[fromY][fromX] == king
9383 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9384 && toY == fromY && toX < fromX-1) {
9385 board[fromY][fromX] = EmptySquare;
9386 board[toY][toX] = king;
9387 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9388 board[fromY][BOARD_LEFT] = EmptySquare;
9389 } else if (fromY == 7 && fromX == 3
9390 && board[fromY][fromX] == BlackKing
9391 && toY == 7 && toX == 5) {
9392 board[fromY][fromX] = EmptySquare;
9393 board[toY][toX] = BlackKing;
9394 board[fromY][7] = EmptySquare;
9395 board[toY][4] = BlackRook;
9396 } else if (fromY == 7 && fromX == 3
9397 && board[fromY][fromX] == BlackKing
9398 && toY == 7 && toX == 1) {
9399 board[fromY][fromX] = EmptySquare;
9400 board[toY][toX] = BlackKing;
9401 board[fromY][0] = EmptySquare;
9402 board[toY][2] = BlackRook;
9403 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9404 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9405 && toY < promoRank && promoChar
9407 /* black pawn promotion */
9408 board[toY][toX] = CharToPiece(ToLower(promoChar));
9409 if(gameInfo.variant==VariantBughouse ||
9410 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9411 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9412 board[fromY][fromX] = EmptySquare;
9413 } else if ((fromY < BOARD_HEIGHT>>1)
9415 && gameInfo.variant != VariantXiangqi
9416 && gameInfo.variant != VariantBerolina
9417 && (board[fromY][fromX] == BlackPawn)
9418 && (board[toY][toX] == EmptySquare)) {
9419 board[fromY][fromX] = EmptySquare;
9420 board[toY][toX] = BlackPawn;
9421 captured = board[toY + 1][toX];
9422 board[toY + 1][toX] = EmptySquare;
9423 } else if ((fromY == 3)
9425 && gameInfo.variant == VariantBerolina
9426 && (board[fromY][fromX] == BlackPawn)
9427 && (board[toY][toX] == EmptySquare)) {
9428 board[fromY][fromX] = EmptySquare;
9429 board[toY][toX] = BlackPawn;
9430 if(oldEP & EP_BEROLIN_A) {
9431 captured = board[fromY][fromX-1];
9432 board[fromY][fromX-1] = EmptySquare;
9433 }else{ captured = board[fromY][fromX+1];
9434 board[fromY][fromX+1] = EmptySquare;
9437 board[toY][toX] = board[fromY][fromX];
9438 board[fromY][fromX] = EmptySquare;
9442 if (gameInfo.holdingsWidth != 0) {
9444 /* !!A lot more code needs to be written to support holdings */
9445 /* [HGM] OK, so I have written it. Holdings are stored in the */
9446 /* penultimate board files, so they are automaticlly stored */
9447 /* in the game history. */
9448 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9449 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9450 /* Delete from holdings, by decreasing count */
9451 /* and erasing image if necessary */
9452 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9453 if(p < (int) BlackPawn) { /* white drop */
9454 p -= (int)WhitePawn;
9455 p = PieceToNumber((ChessSquare)p);
9456 if(p >= gameInfo.holdingsSize) p = 0;
9457 if(--board[p][BOARD_WIDTH-2] <= 0)
9458 board[p][BOARD_WIDTH-1] = EmptySquare;
9459 if((int)board[p][BOARD_WIDTH-2] < 0)
9460 board[p][BOARD_WIDTH-2] = 0;
9461 } else { /* black drop */
9462 p -= (int)BlackPawn;
9463 p = PieceToNumber((ChessSquare)p);
9464 if(p >= gameInfo.holdingsSize) p = 0;
9465 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9466 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9467 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9468 board[BOARD_HEIGHT-1-p][1] = 0;
9471 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9472 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9473 /* [HGM] holdings: Add to holdings, if holdings exist */
9474 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9475 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9476 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9479 if (p >= (int) BlackPawn) {
9480 p -= (int)BlackPawn;
9481 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9482 /* in Shogi restore piece to its original first */
9483 captured = (ChessSquare) (DEMOTED captured);
9486 p = PieceToNumber((ChessSquare)p);
9487 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9488 board[p][BOARD_WIDTH-2]++;
9489 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9491 p -= (int)WhitePawn;
9492 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9493 captured = (ChessSquare) (DEMOTED captured);
9496 p = PieceToNumber((ChessSquare)p);
9497 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9498 board[BOARD_HEIGHT-1-p][1]++;
9499 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9502 } else if (gameInfo.variant == VariantAtomic) {
9503 if (captured != EmptySquare) {
9505 for (y = toY-1; y <= toY+1; y++) {
9506 for (x = toX-1; x <= toX+1; x++) {
9507 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9508 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9509 board[y][x] = EmptySquare;
9513 board[toY][toX] = EmptySquare;
9516 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9517 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9519 if(promoChar == '+') {
9520 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9521 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9522 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9523 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9524 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9525 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9526 board[toY][toX] = newPiece;
9528 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9529 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9530 // [HGM] superchess: take promotion piece out of holdings
9531 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9532 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9533 if(!--board[k][BOARD_WIDTH-2])
9534 board[k][BOARD_WIDTH-1] = EmptySquare;
9536 if(!--board[BOARD_HEIGHT-1-k][1])
9537 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9543 /* Updates forwardMostMove */
9545 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9547 // forwardMostMove++; // [HGM] bare: moved downstream
9549 (void) CoordsToAlgebraic(boards[forwardMostMove],
9550 PosFlags(forwardMostMove),
9551 fromY, fromX, toY, toX, promoChar,
9552 parseList[forwardMostMove]);
9554 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9555 int timeLeft; static int lastLoadFlag=0; int king, piece;
9556 piece = boards[forwardMostMove][fromY][fromX];
9557 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9558 if(gameInfo.variant == VariantKnightmate)
9559 king += (int) WhiteUnicorn - (int) WhiteKing;
9560 if(forwardMostMove == 0) {
9561 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9562 fprintf(serverMoves, "%s;", UserName());
9563 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9564 fprintf(serverMoves, "%s;", second.tidy);
9565 fprintf(serverMoves, "%s;", first.tidy);
9566 if(gameMode == MachinePlaysWhite)
9567 fprintf(serverMoves, "%s;", UserName());
9568 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9569 fprintf(serverMoves, "%s;", second.tidy);
9570 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9571 lastLoadFlag = loadFlag;
9573 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9574 // print castling suffix
9575 if( toY == fromY && piece == king ) {
9577 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9579 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9582 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9583 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9584 boards[forwardMostMove][toY][toX] == EmptySquare
9585 && fromX != toX && fromY != toY)
9586 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9588 if(promoChar != NULLCHAR)
9589 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9591 char buf[MOVE_LEN*2], *p; int len;
9592 fprintf(serverMoves, "/%d/%d",
9593 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9594 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9595 else timeLeft = blackTimeRemaining/1000;
9596 fprintf(serverMoves, "/%d", timeLeft);
9597 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9598 if(p = strchr(buf, '=')) *p = NULLCHAR;
9599 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9600 fprintf(serverMoves, "/%s", buf);
9602 fflush(serverMoves);
9605 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9606 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9609 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9610 if (commentList[forwardMostMove+1] != NULL) {
9611 free(commentList[forwardMostMove+1]);
9612 commentList[forwardMostMove+1] = NULL;
9614 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9615 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9616 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9617 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9618 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9619 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9620 adjustedClock = FALSE;
9621 gameInfo.result = GameUnfinished;
9622 if (gameInfo.resultDetails != NULL) {
9623 free(gameInfo.resultDetails);
9624 gameInfo.resultDetails = NULL;
9626 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9627 moveList[forwardMostMove - 1]);
9628 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9634 if(gameInfo.variant != VariantShogi)
9635 strcat(parseList[forwardMostMove - 1], "+");
9639 strcat(parseList[forwardMostMove - 1], "#");
9645 /* Updates currentMove if not pausing */
9647 ShowMove (int fromX, int fromY, int toX, int toY)
9649 int instant = (gameMode == PlayFromGameFile) ?
9650 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9651 if(appData.noGUI) return;
9652 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9654 if (forwardMostMove == currentMove + 1) {
9655 AnimateMove(boards[forwardMostMove - 1],
9656 fromX, fromY, toX, toY);
9658 if (appData.highlightLastMove) {
9659 SetHighlights(fromX, fromY, toX, toY);
9662 currentMove = forwardMostMove;
9665 if (instant) return;
9667 DisplayMove(currentMove - 1);
9668 DrawPosition(FALSE, boards[currentMove]);
9669 DisplayBothClocks();
9670 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9674 SendEgtPath (ChessProgramState *cps)
9675 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9676 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9678 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9681 char c, *q = name+1, *r, *s;
9683 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9684 while(*p && *p != ',') *q++ = *p++;
9686 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9687 strcmp(name, ",nalimov:") == 0 ) {
9688 // take nalimov path from the menu-changeable option first, if it is defined
9689 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9690 SendToProgram(buf,cps); // send egtbpath command for nalimov
9692 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9693 (s = StrStr(appData.egtFormats, name)) != NULL) {
9694 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9695 s = r = StrStr(s, ":") + 1; // beginning of path info
9696 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9697 c = *r; *r = 0; // temporarily null-terminate path info
9698 *--q = 0; // strip of trailig ':' from name
9699 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9701 SendToProgram(buf,cps); // send egtbpath command for this format
9703 if(*p == ',') p++; // read away comma to position for next format name
9708 InitChessProgram (ChessProgramState *cps, int setup)
9709 /* setup needed to setup FRC opening position */
9711 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9712 if (appData.noChessProgram) return;
9713 hintRequested = FALSE;
9714 bookRequested = FALSE;
9716 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9717 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9718 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9719 if(cps->memSize) { /* [HGM] memory */
9720 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9721 SendToProgram(buf, cps);
9723 SendEgtPath(cps); /* [HGM] EGT */
9724 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9725 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9726 SendToProgram(buf, cps);
9729 SendToProgram(cps->initString, cps);
9730 if (gameInfo.variant != VariantNormal &&
9731 gameInfo.variant != VariantLoadable
9732 /* [HGM] also send variant if board size non-standard */
9733 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9735 char *v = VariantName(gameInfo.variant);
9736 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9737 /* [HGM] in protocol 1 we have to assume all variants valid */
9738 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9739 DisplayFatalError(buf, 0, 1);
9743 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9744 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9745 if( gameInfo.variant == VariantXiangqi )
9746 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9747 if( gameInfo.variant == VariantShogi )
9748 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9749 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9750 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9751 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9752 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9753 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9754 if( gameInfo.variant == VariantCourier )
9755 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9756 if( gameInfo.variant == VariantSuper )
9757 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9758 if( gameInfo.variant == VariantGreat )
9759 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9760 if( gameInfo.variant == VariantSChess )
9761 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9762 if( gameInfo.variant == VariantGrand )
9763 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9766 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9767 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9768 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9769 if(StrStr(cps->variants, b) == NULL) {
9770 // specific sized variant not known, check if general sizing allowed
9771 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9772 if(StrStr(cps->variants, "boardsize") == NULL) {
9773 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9774 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9775 DisplayFatalError(buf, 0, 1);
9778 /* [HGM] here we really should compare with the maximum supported board size */
9781 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9782 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9783 SendToProgram(buf, cps);
9785 currentlyInitializedVariant = gameInfo.variant;
9787 /* [HGM] send opening position in FRC to first engine */
9789 SendToProgram("force\n", cps);
9791 /* engine is now in force mode! Set flag to wake it up after first move. */
9792 setboardSpoiledMachineBlack = 1;
9796 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9797 SendToProgram(buf, cps);
9799 cps->maybeThinking = FALSE;
9800 cps->offeredDraw = 0;
9801 if (!appData.icsActive) {
9802 SendTimeControl(cps, movesPerSession, timeControl,
9803 timeIncrement, appData.searchDepth,
9806 if (appData.showThinking
9807 // [HGM] thinking: four options require thinking output to be sent
9808 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9810 SendToProgram("post\n", cps);
9812 SendToProgram("hard\n", cps);
9813 if (!appData.ponderNextMove) {
9814 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9815 it without being sure what state we are in first. "hard"
9816 is not a toggle, so that one is OK.
9818 SendToProgram("easy\n", cps);
9821 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9822 SendToProgram(buf, cps);
9824 cps->initDone = TRUE;
9825 ClearEngineOutputPane(cps == &second);
9830 StartChessProgram (ChessProgramState *cps)
9835 if (appData.noChessProgram) return;
9836 cps->initDone = FALSE;
9838 if (strcmp(cps->host, "localhost") == 0) {
9839 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9840 } else if (*appData.remoteShell == NULLCHAR) {
9841 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9843 if (*appData.remoteUser == NULLCHAR) {
9844 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9847 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9848 cps->host, appData.remoteUser, cps->program);
9850 err = StartChildProcess(buf, "", &cps->pr);
9854 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9855 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9856 if(cps != &first) return;
9857 appData.noChessProgram = TRUE;
9860 // DisplayFatalError(buf, err, 1);
9861 // cps->pr = NoProc;
9866 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9867 if (cps->protocolVersion > 1) {
9868 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9869 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9870 cps->comboCnt = 0; // and values of combo boxes
9871 SendToProgram(buf, cps);
9873 SendToProgram("xboard\n", cps);
9878 TwoMachinesEventIfReady P((void))
9880 static int curMess = 0;
9881 if (first.lastPing != first.lastPong) {
9882 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9883 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9886 if (second.lastPing != second.lastPong) {
9887 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9888 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9891 DisplayMessage("", ""); curMess = 0;
9897 MakeName (char *template)
9901 static char buf[MSG_SIZ];
9905 clock = time((time_t *)NULL);
9906 tm = localtime(&clock);
9908 while(*p++ = *template++) if(p[-1] == '%') {
9909 switch(*template++) {
9910 case 0: *p = 0; return buf;
9911 case 'Y': i = tm->tm_year+1900; break;
9912 case 'y': i = tm->tm_year-100; break;
9913 case 'M': i = tm->tm_mon+1; break;
9914 case 'd': i = tm->tm_mday; break;
9915 case 'h': i = tm->tm_hour; break;
9916 case 'm': i = tm->tm_min; break;
9917 case 's': i = tm->tm_sec; break;
9920 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9926 CountPlayers (char *p)
9929 while(p = strchr(p, '\n')) p++, n++; // count participants
9934 WriteTourneyFile (char *results, FILE *f)
9935 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9936 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9937 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9938 // create a file with tournament description
9939 fprintf(f, "-participants {%s}\n", appData.participants);
9940 fprintf(f, "-seedBase %d\n", appData.seedBase);
9941 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9942 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9943 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9944 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9945 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9946 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9947 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9948 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9949 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9950 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9951 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9952 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9954 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9956 fprintf(f, "-mps %d\n", appData.movesPerSession);
9957 fprintf(f, "-tc %s\n", appData.timeControl);
9958 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9960 fprintf(f, "-results \"%s\"\n", results);
9965 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9968 Substitute (char *participants, int expunge)
9970 int i, changed, changes=0, nPlayers=0;
9971 char *p, *q, *r, buf[MSG_SIZ];
9972 if(participants == NULL) return;
9973 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9974 r = p = participants; q = appData.participants;
9975 while(*p && *p == *q) {
9976 if(*p == '\n') r = p+1, nPlayers++;
9979 if(*p) { // difference
9980 while(*p && *p++ != '\n');
9981 while(*q && *q++ != '\n');
9983 changes = 1 + (strcmp(p, q) != 0);
9985 if(changes == 1) { // a single engine mnemonic was changed
9986 q = r; while(*q) nPlayers += (*q++ == '\n');
9987 p = buf; while(*r && (*p = *r++) != '\n') p++;
9989 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9990 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9991 if(mnemonic[i]) { // The substitute is valid
9993 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9994 flock(fileno(f), LOCK_EX);
9995 ParseArgsFromFile(f);
9996 fseek(f, 0, SEEK_SET);
9997 FREE(appData.participants); appData.participants = participants;
9998 if(expunge) { // erase results of replaced engine
9999 int len = strlen(appData.results), w, b, dummy;
10000 for(i=0; i<len; i++) {
10001 Pairing(i, nPlayers, &w, &b, &dummy);
10002 if((w == changed || b == changed) && appData.results[i] == '*') {
10003 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10008 for(i=0; i<len; i++) {
10009 Pairing(i, nPlayers, &w, &b, &dummy);
10010 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10013 WriteTourneyFile(appData.results, f);
10014 fclose(f); // release lock
10017 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10019 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10020 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10021 free(participants);
10026 CreateTourney (char *name)
10029 if(matchMode && strcmp(name, appData.tourneyFile)) {
10030 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10032 if(name[0] == NULLCHAR) {
10033 if(appData.participants[0])
10034 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10037 f = fopen(name, "r");
10038 if(f) { // file exists
10039 ASSIGN(appData.tourneyFile, name);
10040 ParseArgsFromFile(f); // parse it
10042 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10043 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10044 DisplayError(_("Not enough participants"), 0);
10047 ASSIGN(appData.tourneyFile, name);
10048 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10049 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10052 appData.noChessProgram = FALSE;
10053 appData.clockMode = TRUE;
10059 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10061 char buf[MSG_SIZ], *p, *q;
10062 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10063 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10064 skip = !all && group[0]; // if group requested, we start in skip mode
10065 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10066 p = names; q = buf; header = 0;
10067 while(*p && *p != '\n') *q++ = *p++;
10069 if(*p == '\n') p++;
10070 if(buf[0] == '#') {
10071 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10072 depth++; // we must be entering a new group
10073 if(all) continue; // suppress printing group headers when complete list requested
10075 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10077 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10078 if(engineList[i]) free(engineList[i]);
10079 engineList[i] = strdup(buf);
10080 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10081 if(engineMnemonic[i]) free(engineMnemonic[i]);
10082 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10084 sscanf(q + 8, "%s", buf + strlen(buf));
10087 engineMnemonic[i] = strdup(buf);
10090 engineList[i] = engineMnemonic[i] = NULL;
10094 // following implemented as macro to avoid type limitations
10095 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10098 SwapEngines (int n)
10099 { // swap settings for first engine and other engine (so far only some selected options)
10104 SWAP(chessProgram, p)
10106 SWAP(hasOwnBookUCI, h)
10107 SWAP(protocolVersion, h)
10109 SWAP(scoreIsAbsolute, h)
10114 SWAP(engOptions, p)
10115 SWAP(engInitString, p)
10116 SWAP(computerString, p)
10118 SWAP(fenOverride, p)
10120 SWAP(accumulateTC, h)
10125 SetPlayer (int player, char *p)
10126 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10128 char buf[MSG_SIZ], *engineName;
10129 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10130 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10131 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10133 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10134 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10135 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10136 ParseArgsFromString(buf);
10142 char *recentEngines;
10145 RecentEngineEvent (int nr)
10148 // SwapEngines(1); // bump first to second
10149 // ReplaceEngine(&second, 1); // and load it there
10150 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10151 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10152 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10153 ReplaceEngine(&first, 0);
10154 FloatToFront(&appData.recentEngineList, command[n]);
10159 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10160 { // determine players from game number
10161 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10163 if(appData.tourneyType == 0) {
10164 roundsPerCycle = (nPlayers - 1) | 1;
10165 pairingsPerRound = nPlayers / 2;
10166 } else if(appData.tourneyType > 0) {
10167 roundsPerCycle = nPlayers - appData.tourneyType;
10168 pairingsPerRound = appData.tourneyType;
10170 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10171 gamesPerCycle = gamesPerRound * roundsPerCycle;
10172 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10173 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10174 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10175 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10176 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10177 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10179 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10180 if(appData.roundSync) *syncInterval = gamesPerRound;
10182 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10184 if(appData.tourneyType == 0) {
10185 if(curPairing == (nPlayers-1)/2 ) {
10186 *whitePlayer = curRound;
10187 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10189 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10190 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10191 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10192 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10194 } else if(appData.tourneyType > 1) {
10195 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10196 *whitePlayer = curRound + appData.tourneyType;
10197 } else if(appData.tourneyType > 0) {
10198 *whitePlayer = curPairing;
10199 *blackPlayer = curRound + appData.tourneyType;
10202 // take care of white/black alternation per round.
10203 // For cycles and games this is already taken care of by default, derived from matchGame!
10204 return curRound & 1;
10208 NextTourneyGame (int nr, int *swapColors)
10209 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10211 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10213 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10214 tf = fopen(appData.tourneyFile, "r");
10215 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10216 ParseArgsFromFile(tf); fclose(tf);
10217 InitTimeControls(); // TC might be altered from tourney file
10219 nPlayers = CountPlayers(appData.participants); // count participants
10220 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10221 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10224 p = q = appData.results;
10225 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10226 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10227 DisplayMessage(_("Waiting for other game(s)"),"");
10228 waitingForGame = TRUE;
10229 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10232 waitingForGame = FALSE;
10235 if(appData.tourneyType < 0) {
10236 if(nr>=0 && !pairingReceived) {
10238 if(pairing.pr == NoProc) {
10239 if(!appData.pairingEngine[0]) {
10240 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10243 StartChessProgram(&pairing); // starts the pairing engine
10245 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10246 SendToProgram(buf, &pairing);
10247 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10248 SendToProgram(buf, &pairing);
10249 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10251 pairingReceived = 0; // ... so we continue here
10253 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10254 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10255 matchGame = 1; roundNr = nr / syncInterval + 1;
10258 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10260 // redefine engines, engine dir, etc.
10261 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10262 if(first.pr == NoProc) {
10263 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10264 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10266 if(second.pr == NoProc) {
10268 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10269 SwapEngines(1); // and make that valid for second engine by swapping
10270 InitEngine(&second, 1);
10272 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10273 UpdateLogos(FALSE); // leave display to ModeHiglight()
10279 { // performs game initialization that does not invoke engines, and then tries to start the game
10280 int res, firstWhite, swapColors = 0;
10281 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10282 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
10284 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10285 if(strcmp(buf, currentDebugFile)) { // name has changed
10286 FILE *f = fopen(buf, "w");
10287 if(f) { // if opening the new file failed, just keep using the old one
10288 ASSIGN(currentDebugFile, buf);
10292 if(appData.serverFileName) {
10293 if(serverFP) fclose(serverFP);
10294 serverFP = fopen(appData.serverFileName, "w");
10295 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10296 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10300 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10301 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10302 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10303 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10304 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10305 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10306 Reset(FALSE, first.pr != NoProc);
10307 res = LoadGameOrPosition(matchGame); // setup game
10308 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10309 if(!res) return; // abort when bad game/pos file
10310 TwoMachinesEvent();
10314 UserAdjudicationEvent (int result)
10316 ChessMove gameResult = GameIsDrawn;
10319 gameResult = WhiteWins;
10321 else if( result < 0 ) {
10322 gameResult = BlackWins;
10325 if( gameMode == TwoMachinesPlay ) {
10326 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10331 // [HGM] save: calculate checksum of game to make games easily identifiable
10333 StringCheckSum (char *s)
10336 if(s==NULL) return 0;
10337 while(*s) i = i*259 + *s++;
10345 for(i=backwardMostMove; i<forwardMostMove; i++) {
10346 sum += pvInfoList[i].depth;
10347 sum += StringCheckSum(parseList[i]);
10348 sum += StringCheckSum(commentList[i]);
10351 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10352 return sum + StringCheckSum(commentList[i]);
10353 } // end of save patch
10356 GameEnds (ChessMove result, char *resultDetails, int whosays)
10358 GameMode nextGameMode;
10360 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10362 if(endingGame) return; /* [HGM] crash: forbid recursion */
10364 if(twoBoards) { // [HGM] dual: switch back to one board
10365 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10366 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10368 if (appData.debugMode) {
10369 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10370 result, resultDetails ? resultDetails : "(null)", whosays);
10373 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10375 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10376 /* If we are playing on ICS, the server decides when the
10377 game is over, but the engine can offer to draw, claim
10381 if (appData.zippyPlay && first.initDone) {
10382 if (result == GameIsDrawn) {
10383 /* In case draw still needs to be claimed */
10384 SendToICS(ics_prefix);
10385 SendToICS("draw\n");
10386 } else if (StrCaseStr(resultDetails, "resign")) {
10387 SendToICS(ics_prefix);
10388 SendToICS("resign\n");
10392 endingGame = 0; /* [HGM] crash */
10396 /* If we're loading the game from a file, stop */
10397 if (whosays == GE_FILE) {
10398 (void) StopLoadGameTimer();
10402 /* Cancel draw offers */
10403 first.offeredDraw = second.offeredDraw = 0;
10405 /* If this is an ICS game, only ICS can really say it's done;
10406 if not, anyone can. */
10407 isIcsGame = (gameMode == IcsPlayingWhite ||
10408 gameMode == IcsPlayingBlack ||
10409 gameMode == IcsObserving ||
10410 gameMode == IcsExamining);
10412 if (!isIcsGame || whosays == GE_ICS) {
10413 /* OK -- not an ICS game, or ICS said it was done */
10415 if (!isIcsGame && !appData.noChessProgram)
10416 SetUserThinkingEnables();
10418 /* [HGM] if a machine claims the game end we verify this claim */
10419 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10420 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10422 ChessMove trueResult = (ChessMove) -1;
10424 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10425 first.twoMachinesColor[0] :
10426 second.twoMachinesColor[0] ;
10428 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10429 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10430 /* [HGM] verify: engine mate claims accepted if they were flagged */
10431 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10433 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10434 /* [HGM] verify: engine mate claims accepted if they were flagged */
10435 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10437 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10438 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10441 // now verify win claims, but not in drop games, as we don't understand those yet
10442 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10443 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10444 (result == WhiteWins && claimer == 'w' ||
10445 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10446 if (appData.debugMode) {
10447 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10448 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10450 if(result != trueResult) {
10451 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10452 result = claimer == 'w' ? BlackWins : WhiteWins;
10453 resultDetails = buf;
10456 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10457 && (forwardMostMove <= backwardMostMove ||
10458 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10459 (claimer=='b')==(forwardMostMove&1))
10461 /* [HGM] verify: draws that were not flagged are false claims */
10462 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10463 result = claimer == 'w' ? BlackWins : WhiteWins;
10464 resultDetails = buf;
10466 /* (Claiming a loss is accepted no questions asked!) */
10468 /* [HGM] bare: don't allow bare King to win */
10469 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10470 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10471 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10472 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10473 && result != GameIsDrawn)
10474 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10475 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10476 int p = (signed char)boards[forwardMostMove][i][j] - color;
10477 if(p >= 0 && p <= (int)WhiteKing) k++;
10479 if (appData.debugMode) {
10480 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10481 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10484 result = GameIsDrawn;
10485 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10486 resultDetails = buf;
10492 if(serverMoves != NULL && !loadFlag) { char c = '=';
10493 if(result==WhiteWins) c = '+';
10494 if(result==BlackWins) c = '-';
10495 if(resultDetails != NULL)
10496 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10498 if (resultDetails != NULL) {
10499 gameInfo.result = result;
10500 gameInfo.resultDetails = StrSave(resultDetails);
10502 /* display last move only if game was not loaded from file */
10503 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10504 DisplayMove(currentMove - 1);
10506 if (forwardMostMove != 0) {
10507 if (gameMode != PlayFromGameFile && gameMode != EditGame
10508 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10510 if (*appData.saveGameFile != NULLCHAR) {
10511 SaveGameToFile(appData.saveGameFile, TRUE);
10512 } else if (appData.autoSaveGames) {
10515 if (*appData.savePositionFile != NULLCHAR) {
10516 SavePositionToFile(appData.savePositionFile);
10521 /* Tell program how game ended in case it is learning */
10522 /* [HGM] Moved this to after saving the PGN, just in case */
10523 /* engine died and we got here through time loss. In that */
10524 /* case we will get a fatal error writing the pipe, which */
10525 /* would otherwise lose us the PGN. */
10526 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10527 /* output during GameEnds should never be fatal anymore */
10528 if (gameMode == MachinePlaysWhite ||
10529 gameMode == MachinePlaysBlack ||
10530 gameMode == TwoMachinesPlay ||
10531 gameMode == IcsPlayingWhite ||
10532 gameMode == IcsPlayingBlack ||
10533 gameMode == BeginningOfGame) {
10535 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10537 if (first.pr != NoProc) {
10538 SendToProgram(buf, &first);
10540 if (second.pr != NoProc &&
10541 gameMode == TwoMachinesPlay) {
10542 SendToProgram(buf, &second);
10547 if (appData.icsActive) {
10548 if (appData.quietPlay &&
10549 (gameMode == IcsPlayingWhite ||
10550 gameMode == IcsPlayingBlack)) {
10551 SendToICS(ics_prefix);
10552 SendToICS("set shout 1\n");
10554 nextGameMode = IcsIdle;
10555 ics_user_moved = FALSE;
10556 /* clean up premove. It's ugly when the game has ended and the
10557 * premove highlights are still on the board.
10560 gotPremove = FALSE;
10561 ClearPremoveHighlights();
10562 DrawPosition(FALSE, boards[currentMove]);
10564 if (whosays == GE_ICS) {
10567 if (gameMode == IcsPlayingWhite)
10569 else if(gameMode == IcsPlayingBlack)
10570 PlayIcsLossSound();
10573 if (gameMode == IcsPlayingBlack)
10575 else if(gameMode == IcsPlayingWhite)
10576 PlayIcsLossSound();
10579 PlayIcsDrawSound();
10582 PlayIcsUnfinishedSound();
10585 } else if (gameMode == EditGame ||
10586 gameMode == PlayFromGameFile ||
10587 gameMode == AnalyzeMode ||
10588 gameMode == AnalyzeFile) {
10589 nextGameMode = gameMode;
10591 nextGameMode = EndOfGame;
10596 nextGameMode = gameMode;
10599 if (appData.noChessProgram) {
10600 gameMode = nextGameMode;
10602 endingGame = 0; /* [HGM] crash */
10607 /* Put first chess program into idle state */
10608 if (first.pr != NoProc &&
10609 (gameMode == MachinePlaysWhite ||
10610 gameMode == MachinePlaysBlack ||
10611 gameMode == TwoMachinesPlay ||
10612 gameMode == IcsPlayingWhite ||
10613 gameMode == IcsPlayingBlack ||
10614 gameMode == BeginningOfGame)) {
10615 SendToProgram("force\n", &first);
10616 if (first.usePing) {
10618 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10619 SendToProgram(buf, &first);
10622 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10623 /* Kill off first chess program */
10624 if (first.isr != NULL)
10625 RemoveInputSource(first.isr);
10628 if (first.pr != NoProc) {
10630 DoSleep( appData.delayBeforeQuit );
10631 SendToProgram("quit\n", &first);
10632 DoSleep( appData.delayAfterQuit );
10633 DestroyChildProcess(first.pr, first.useSigterm);
10637 if (second.reuse) {
10638 /* Put second chess program into idle state */
10639 if (second.pr != NoProc &&
10640 gameMode == TwoMachinesPlay) {
10641 SendToProgram("force\n", &second);
10642 if (second.usePing) {
10644 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10645 SendToProgram(buf, &second);
10648 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10649 /* Kill off second chess program */
10650 if (second.isr != NULL)
10651 RemoveInputSource(second.isr);
10654 if (second.pr != NoProc) {
10655 DoSleep( appData.delayBeforeQuit );
10656 SendToProgram("quit\n", &second);
10657 DoSleep( appData.delayAfterQuit );
10658 DestroyChildProcess(second.pr, second.useSigterm);
10660 second.pr = NoProc;
10663 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10664 char resChar = '=';
10668 if (first.twoMachinesColor[0] == 'w') {
10671 second.matchWins++;
10676 if (first.twoMachinesColor[0] == 'b') {
10679 second.matchWins++;
10682 case GameUnfinished:
10688 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10689 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10690 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10691 ReserveGame(nextGame, resChar); // sets nextGame
10692 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10693 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10694 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10696 if (nextGame <= appData.matchGames && !abortMatch) {
10697 gameMode = nextGameMode;
10698 matchGame = nextGame; // this will be overruled in tourney mode!
10699 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10700 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10701 endingGame = 0; /* [HGM] crash */
10704 gameMode = nextGameMode;
10705 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10706 first.tidy, second.tidy,
10707 first.matchWins, second.matchWins,
10708 appData.matchGames - (first.matchWins + second.matchWins));
10709 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10710 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10711 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10712 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10713 first.twoMachinesColor = "black\n";
10714 second.twoMachinesColor = "white\n";
10716 first.twoMachinesColor = "white\n";
10717 second.twoMachinesColor = "black\n";
10721 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10722 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10724 gameMode = nextGameMode;
10726 endingGame = 0; /* [HGM] crash */
10727 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10728 if(matchMode == TRUE) { // match through command line: exit with or without popup
10730 ToNrEvent(forwardMostMove);
10731 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10733 } else DisplayFatalError(buf, 0, 0);
10734 } else { // match through menu; just stop, with or without popup
10735 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10738 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10739 } else DisplayNote(buf);
10741 if(ranking) free(ranking);
10745 /* Assumes program was just initialized (initString sent).
10746 Leaves program in force mode. */
10748 FeedMovesToProgram (ChessProgramState *cps, int upto)
10752 if (appData.debugMode)
10753 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10754 startedFromSetupPosition ? "position and " : "",
10755 backwardMostMove, upto, cps->which);
10756 if(currentlyInitializedVariant != gameInfo.variant) {
10758 // [HGM] variantswitch: make engine aware of new variant
10759 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10760 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10761 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10762 SendToProgram(buf, cps);
10763 currentlyInitializedVariant = gameInfo.variant;
10765 SendToProgram("force\n", cps);
10766 if (startedFromSetupPosition) {
10767 SendBoard(cps, backwardMostMove);
10768 if (appData.debugMode) {
10769 fprintf(debugFP, "feedMoves\n");
10772 for (i = backwardMostMove; i < upto; i++) {
10773 SendMoveToProgram(i, cps);
10779 ResurrectChessProgram ()
10781 /* The chess program may have exited.
10782 If so, restart it and feed it all the moves made so far. */
10783 static int doInit = 0;
10785 if (appData.noChessProgram) return 1;
10787 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10788 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10789 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10790 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10792 if (first.pr != NoProc) return 1;
10793 StartChessProgram(&first);
10795 InitChessProgram(&first, FALSE);
10796 FeedMovesToProgram(&first, currentMove);
10798 if (!first.sendTime) {
10799 /* can't tell gnuchess what its clock should read,
10800 so we bow to its notion. */
10802 timeRemaining[0][currentMove] = whiteTimeRemaining;
10803 timeRemaining[1][currentMove] = blackTimeRemaining;
10806 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10807 appData.icsEngineAnalyze) && first.analysisSupport) {
10808 SendToProgram("analyze\n", &first);
10809 first.analyzing = TRUE;
10815 * Button procedures
10818 Reset (int redraw, int init)
10822 if (appData.debugMode) {
10823 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10824 redraw, init, gameMode);
10826 CleanupTail(); // [HGM] vari: delete any stored variations
10827 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10828 pausing = pauseExamInvalid = FALSE;
10829 startedFromSetupPosition = blackPlaysFirst = FALSE;
10831 whiteFlag = blackFlag = FALSE;
10832 userOfferedDraw = FALSE;
10833 hintRequested = bookRequested = FALSE;
10834 first.maybeThinking = FALSE;
10835 second.maybeThinking = FALSE;
10836 first.bookSuspend = FALSE; // [HGM] book
10837 second.bookSuspend = FALSE;
10838 thinkOutput[0] = NULLCHAR;
10839 lastHint[0] = NULLCHAR;
10840 ClearGameInfo(&gameInfo);
10841 gameInfo.variant = StringToVariant(appData.variant);
10842 ics_user_moved = ics_clock_paused = FALSE;
10843 ics_getting_history = H_FALSE;
10845 white_holding[0] = black_holding[0] = NULLCHAR;
10846 ClearProgramStats();
10847 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10851 flipView = appData.flipView;
10852 ClearPremoveHighlights();
10853 gotPremove = FALSE;
10854 alarmSounded = FALSE;
10856 GameEnds(EndOfFile, NULL, GE_PLAYER);
10857 if(appData.serverMovesName != NULL) {
10858 /* [HGM] prepare to make moves file for broadcasting */
10859 clock_t t = clock();
10860 if(serverMoves != NULL) fclose(serverMoves);
10861 serverMoves = fopen(appData.serverMovesName, "r");
10862 if(serverMoves != NULL) {
10863 fclose(serverMoves);
10864 /* delay 15 sec before overwriting, so all clients can see end */
10865 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10867 serverMoves = fopen(appData.serverMovesName, "w");
10871 gameMode = BeginningOfGame;
10873 if(appData.icsActive) gameInfo.variant = VariantNormal;
10874 currentMove = forwardMostMove = backwardMostMove = 0;
10875 MarkTargetSquares(1);
10876 InitPosition(redraw);
10877 for (i = 0; i < MAX_MOVES; i++) {
10878 if (commentList[i] != NULL) {
10879 free(commentList[i]);
10880 commentList[i] = NULL;
10884 timeRemaining[0][0] = whiteTimeRemaining;
10885 timeRemaining[1][0] = blackTimeRemaining;
10887 if (first.pr == NoProc) {
10888 StartChessProgram(&first);
10891 InitChessProgram(&first, startedFromSetupPosition);
10894 DisplayMessage("", "");
10895 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10896 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10897 ClearMap(); // [HGM] exclude: invalidate map
10901 AutoPlayGameLoop ()
10904 if (!AutoPlayOneMove())
10906 if (matchMode || appData.timeDelay == 0)
10908 if (appData.timeDelay < 0)
10910 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10919 int fromX, fromY, toX, toY;
10921 if (appData.debugMode) {
10922 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10925 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10928 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10929 pvInfoList[currentMove].depth = programStats.depth;
10930 pvInfoList[currentMove].score = programStats.score;
10931 pvInfoList[currentMove].time = 0;
10932 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10935 if (currentMove >= forwardMostMove) {
10936 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10937 // gameMode = EndOfGame;
10938 // ModeHighlight();
10940 /* [AS] Clear current move marker at the end of a game */
10941 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10946 toX = moveList[currentMove][2] - AAA;
10947 toY = moveList[currentMove][3] - ONE;
10949 if (moveList[currentMove][1] == '@') {
10950 if (appData.highlightLastMove) {
10951 SetHighlights(-1, -1, toX, toY);
10954 fromX = moveList[currentMove][0] - AAA;
10955 fromY = moveList[currentMove][1] - ONE;
10957 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10959 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10961 if (appData.highlightLastMove) {
10962 SetHighlights(fromX, fromY, toX, toY);
10965 DisplayMove(currentMove);
10966 SendMoveToProgram(currentMove++, &first);
10967 DisplayBothClocks();
10968 DrawPosition(FALSE, boards[currentMove]);
10969 // [HGM] PV info: always display, routine tests if empty
10970 DisplayComment(currentMove - 1, commentList[currentMove]);
10976 LoadGameOneMove (ChessMove readAhead)
10978 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10979 char promoChar = NULLCHAR;
10980 ChessMove moveType;
10981 char move[MSG_SIZ];
10984 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10985 gameMode != AnalyzeMode && gameMode != Training) {
10990 yyboardindex = forwardMostMove;
10991 if (readAhead != EndOfFile) {
10992 moveType = readAhead;
10994 if (gameFileFP == NULL)
10996 moveType = (ChessMove) Myylex();
11000 switch (moveType) {
11002 if (appData.debugMode)
11003 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11006 /* append the comment but don't display it */
11007 AppendComment(currentMove, p, FALSE);
11010 case WhiteCapturesEnPassant:
11011 case BlackCapturesEnPassant:
11012 case WhitePromotion:
11013 case BlackPromotion:
11014 case WhiteNonPromotion:
11015 case BlackNonPromotion:
11017 case WhiteKingSideCastle:
11018 case WhiteQueenSideCastle:
11019 case BlackKingSideCastle:
11020 case BlackQueenSideCastle:
11021 case WhiteKingSideCastleWild:
11022 case WhiteQueenSideCastleWild:
11023 case BlackKingSideCastleWild:
11024 case BlackQueenSideCastleWild:
11026 case WhiteHSideCastleFR:
11027 case WhiteASideCastleFR:
11028 case BlackHSideCastleFR:
11029 case BlackASideCastleFR:
11031 if (appData.debugMode)
11032 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11033 fromX = currentMoveString[0] - AAA;
11034 fromY = currentMoveString[1] - ONE;
11035 toX = currentMoveString[2] - AAA;
11036 toY = currentMoveString[3] - ONE;
11037 promoChar = currentMoveString[4];
11042 if (appData.debugMode)
11043 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11044 fromX = moveType == WhiteDrop ?
11045 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11046 (int) CharToPiece(ToLower(currentMoveString[0]));
11048 toX = currentMoveString[2] - AAA;
11049 toY = currentMoveString[3] - ONE;
11055 case GameUnfinished:
11056 if (appData.debugMode)
11057 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11058 p = strchr(yy_text, '{');
11059 if (p == NULL) p = strchr(yy_text, '(');
11062 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11064 q = strchr(p, *p == '{' ? '}' : ')');
11065 if (q != NULL) *q = NULLCHAR;
11068 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11069 GameEnds(moveType, p, GE_FILE);
11071 if (cmailMsgLoaded) {
11073 flipView = WhiteOnMove(currentMove);
11074 if (moveType == GameUnfinished) flipView = !flipView;
11075 if (appData.debugMode)
11076 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11081 if (appData.debugMode)
11082 fprintf(debugFP, "Parser hit end of file\n");
11083 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11089 if (WhiteOnMove(currentMove)) {
11090 GameEnds(BlackWins, "Black mates", GE_FILE);
11092 GameEnds(WhiteWins, "White mates", GE_FILE);
11096 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11102 case MoveNumberOne:
11103 if (lastLoadGameStart == GNUChessGame) {
11104 /* GNUChessGames have numbers, but they aren't move numbers */
11105 if (appData.debugMode)
11106 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11107 yy_text, (int) moveType);
11108 return LoadGameOneMove(EndOfFile); /* tail recursion */
11110 /* else fall thru */
11115 /* Reached start of next game in file */
11116 if (appData.debugMode)
11117 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11118 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11124 if (WhiteOnMove(currentMove)) {
11125 GameEnds(BlackWins, "Black mates", GE_FILE);
11127 GameEnds(WhiteWins, "White mates", GE_FILE);
11131 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11137 case PositionDiagram: /* should not happen; ignore */
11138 case ElapsedTime: /* ignore */
11139 case NAG: /* ignore */
11140 if (appData.debugMode)
11141 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11142 yy_text, (int) moveType);
11143 return LoadGameOneMove(EndOfFile); /* tail recursion */
11146 if (appData.testLegality) {
11147 if (appData.debugMode)
11148 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11149 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11150 (forwardMostMove / 2) + 1,
11151 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11152 DisplayError(move, 0);
11155 if (appData.debugMode)
11156 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11157 yy_text, currentMoveString);
11158 fromX = currentMoveString[0] - AAA;
11159 fromY = currentMoveString[1] - ONE;
11160 toX = currentMoveString[2] - AAA;
11161 toY = currentMoveString[3] - ONE;
11162 promoChar = currentMoveString[4];
11166 case AmbiguousMove:
11167 if (appData.debugMode)
11168 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11169 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11170 (forwardMostMove / 2) + 1,
11171 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11172 DisplayError(move, 0);
11177 case ImpossibleMove:
11178 if (appData.debugMode)
11179 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11180 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11181 (forwardMostMove / 2) + 1,
11182 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11183 DisplayError(move, 0);
11189 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11190 DrawPosition(FALSE, boards[currentMove]);
11191 DisplayBothClocks();
11192 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11193 DisplayComment(currentMove - 1, commentList[currentMove]);
11195 (void) StopLoadGameTimer();
11197 cmailOldMove = forwardMostMove;
11200 /* currentMoveString is set as a side-effect of yylex */
11202 thinkOutput[0] = NULLCHAR;
11203 MakeMove(fromX, fromY, toX, toY, promoChar);
11204 currentMove = forwardMostMove;
11209 /* Load the nth game from the given file */
11211 LoadGameFromFile (char *filename, int n, char *title, int useList)
11216 if (strcmp(filename, "-") == 0) {
11220 f = fopen(filename, "rb");
11222 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11223 DisplayError(buf, errno);
11227 if (fseek(f, 0, 0) == -1) {
11228 /* f is not seekable; probably a pipe */
11231 if (useList && n == 0) {
11232 int error = GameListBuild(f);
11234 DisplayError(_("Cannot build game list"), error);
11235 } else if (!ListEmpty(&gameList) &&
11236 ((ListGame *) gameList.tailPred)->number > 1) {
11237 GameListPopUp(f, title);
11244 return LoadGame(f, n, title, FALSE);
11249 MakeRegisteredMove ()
11251 int fromX, fromY, toX, toY;
11253 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11254 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11257 if (appData.debugMode)
11258 fprintf(debugFP, "Restoring %s for game %d\n",
11259 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11261 thinkOutput[0] = NULLCHAR;
11262 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11263 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11264 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11265 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11266 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11267 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11268 MakeMove(fromX, fromY, toX, toY, promoChar);
11269 ShowMove(fromX, fromY, toX, toY);
11271 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11278 if (WhiteOnMove(currentMove)) {
11279 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11281 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11286 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11293 if (WhiteOnMove(currentMove)) {
11294 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11296 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11301 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11312 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11314 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11318 if (gameNumber > nCmailGames) {
11319 DisplayError(_("No more games in this message"), 0);
11322 if (f == lastLoadGameFP) {
11323 int offset = gameNumber - lastLoadGameNumber;
11325 cmailMsg[0] = NULLCHAR;
11326 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11327 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11328 nCmailMovesRegistered--;
11330 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11331 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11332 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11335 if (! RegisterMove()) return FALSE;
11339 retVal = LoadGame(f, gameNumber, title, useList);
11341 /* Make move registered during previous look at this game, if any */
11342 MakeRegisteredMove();
11344 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11345 commentList[currentMove]
11346 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11347 DisplayComment(currentMove - 1, commentList[currentMove]);
11353 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11355 ReloadGame (int offset)
11357 int gameNumber = lastLoadGameNumber + offset;
11358 if (lastLoadGameFP == NULL) {
11359 DisplayError(_("No game has been loaded yet"), 0);
11362 if (gameNumber <= 0) {
11363 DisplayError(_("Can't back up any further"), 0);
11366 if (cmailMsgLoaded) {
11367 return CmailLoadGame(lastLoadGameFP, gameNumber,
11368 lastLoadGameTitle, lastLoadGameUseList);
11370 return LoadGame(lastLoadGameFP, gameNumber,
11371 lastLoadGameTitle, lastLoadGameUseList);
11375 int keys[EmptySquare+1];
11378 PositionMatches (Board b1, Board b2)
11381 switch(appData.searchMode) {
11382 case 1: return CompareWithRights(b1, b2);
11384 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11385 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11389 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11391 sum += keys[b1[r][f]] - keys[b2[r][f]];
11395 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11396 sum += keys[b1[r][f]] - keys[b2[r][f]];
11408 int pieceList[256], quickBoard[256];
11409 ChessSquare pieceType[256] = { EmptySquare };
11410 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11411 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11412 int soughtTotal, turn;
11413 Boolean epOK, flipSearch;
11416 unsigned char piece, to;
11419 #define DSIZE (250000)
11421 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11422 Move *moveDatabase = initialSpace;
11423 unsigned int movePtr, dataSize = DSIZE;
11426 MakePieceList (Board board, int *counts)
11428 int r, f, n=Q_PROMO, total=0;
11429 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11430 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11431 int sq = f + (r<<4);
11432 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11433 quickBoard[sq] = ++n;
11435 pieceType[n] = board[r][f];
11436 counts[board[r][f]]++;
11437 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11438 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11442 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11447 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11449 int sq = fromX + (fromY<<4);
11450 int piece = quickBoard[sq];
11451 quickBoard[sq] = 0;
11452 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11453 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11454 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11455 moveDatabase[movePtr++].piece = Q_WCASTL;
11456 quickBoard[sq] = piece;
11457 piece = quickBoard[from]; quickBoard[from] = 0;
11458 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11460 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11461 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11462 moveDatabase[movePtr++].piece = Q_BCASTL;
11463 quickBoard[sq] = piece;
11464 piece = quickBoard[from]; quickBoard[from] = 0;
11465 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11467 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11468 quickBoard[(fromY<<4)+toX] = 0;
11469 moveDatabase[movePtr].piece = Q_EP;
11470 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11471 moveDatabase[movePtr].to = sq;
11473 if(promoPiece != pieceType[piece]) {
11474 moveDatabase[movePtr++].piece = Q_PROMO;
11475 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11477 moveDatabase[movePtr].piece = piece;
11478 quickBoard[sq] = piece;
11483 PackGame (Board board)
11485 Move *newSpace = NULL;
11486 moveDatabase[movePtr].piece = 0; // terminate previous game
11487 if(movePtr > dataSize) {
11488 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11489 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11490 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11493 Move *p = moveDatabase, *q = newSpace;
11494 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11495 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11496 moveDatabase = newSpace;
11497 } else { // calloc failed, we must be out of memory. Too bad...
11498 dataSize = 0; // prevent calloc events for all subsequent games
11499 return 0; // and signal this one isn't cached
11503 MakePieceList(board, counts);
11508 QuickCompare (Board board, int *minCounts, int *maxCounts)
11509 { // compare according to search mode
11511 switch(appData.searchMode)
11513 case 1: // exact position match
11514 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11515 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11516 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11519 case 2: // can have extra material on empty squares
11520 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11521 if(board[r][f] == EmptySquare) continue;
11522 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11525 case 3: // material with exact Pawn structure
11526 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11527 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11528 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11529 } // fall through to material comparison
11530 case 4: // exact material
11531 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11533 case 6: // material range with given imbalance
11534 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11535 // fall through to range comparison
11536 case 5: // material range
11537 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11543 QuickScan (Board board, Move *move)
11544 { // reconstruct game,and compare all positions in it
11545 int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11547 int piece = move->piece;
11548 int to = move->to, from = pieceList[piece];
11549 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11550 if(!piece) return -1;
11551 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11552 piece = (++move)->piece;
11553 from = pieceList[piece];
11554 counts[pieceType[piece]]--;
11555 pieceType[piece] = (ChessSquare) move->to;
11556 counts[move->to]++;
11557 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11558 counts[pieceType[quickBoard[to]]]--;
11559 quickBoard[to] = 0; total--;
11562 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11564 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11565 from = pieceList[piece]; // so this must be King
11566 quickBoard[from] = 0;
11567 pieceList[piece] = to;
11568 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11569 quickBoard[from] = 0; // rook
11570 quickBoard[to] = piece;
11571 to = move->to; piece = move->piece;
11575 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11576 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11577 quickBoard[from] = 0;
11579 quickBoard[to] = piece;
11580 pieceList[piece] = to;
11582 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11583 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11584 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11585 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11587 static int lastCounts[EmptySquare+1];
11589 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11590 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11591 } else stretch = 0;
11592 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11593 move++; delayedKing = -1;
11601 flipSearch = FALSE;
11602 CopyBoard(soughtBoard, boards[currentMove]);
11603 soughtTotal = MakePieceList(soughtBoard, maxSought);
11604 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11605 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11606 CopyBoard(reverseBoard, boards[currentMove]);
11607 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11608 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11609 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11610 reverseBoard[r][f] = piece;
11612 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11613 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11614 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11615 || (boards[currentMove][CASTLING][2] == NoRights ||
11616 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11617 && (boards[currentMove][CASTLING][5] == NoRights ||
11618 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11621 CopyBoard(flipBoard, soughtBoard);
11622 CopyBoard(rotateBoard, reverseBoard);
11623 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11624 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11625 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11628 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11629 if(appData.searchMode >= 5) {
11630 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11631 MakePieceList(soughtBoard, minSought);
11632 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11634 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11635 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11638 GameInfo dummyInfo;
11641 GameContainsPosition (FILE *f, ListGame *lg)
11643 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11644 int fromX, fromY, toX, toY;
11646 static int initDone=FALSE;
11648 // weed out games based on numerical tag comparison
11649 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11650 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11651 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11652 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11654 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11657 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11658 else CopyBoard(boards[scratch], initialPosition); // default start position
11661 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11662 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11665 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11666 fseek(f, lg->offset, 0);
11669 yyboardindex = scratch;
11670 quickFlag = plyNr+1;
11675 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11681 if(plyNr) return -1; // after we have seen moves, this is for new game
11684 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11685 case ImpossibleMove:
11686 case WhiteWins: // game ends here with these four
11689 case GameUnfinished:
11693 if(appData.testLegality) return -1;
11694 case WhiteCapturesEnPassant:
11695 case BlackCapturesEnPassant:
11696 case WhitePromotion:
11697 case BlackPromotion:
11698 case WhiteNonPromotion:
11699 case BlackNonPromotion:
11701 case WhiteKingSideCastle:
11702 case WhiteQueenSideCastle:
11703 case BlackKingSideCastle:
11704 case BlackQueenSideCastle:
11705 case WhiteKingSideCastleWild:
11706 case WhiteQueenSideCastleWild:
11707 case BlackKingSideCastleWild:
11708 case BlackQueenSideCastleWild:
11709 case WhiteHSideCastleFR:
11710 case WhiteASideCastleFR:
11711 case BlackHSideCastleFR:
11712 case BlackASideCastleFR:
11713 fromX = currentMoveString[0] - AAA;
11714 fromY = currentMoveString[1] - ONE;
11715 toX = currentMoveString[2] - AAA;
11716 toY = currentMoveString[3] - ONE;
11717 promoChar = currentMoveString[4];
11721 fromX = next == WhiteDrop ?
11722 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11723 (int) CharToPiece(ToLower(currentMoveString[0]));
11725 toX = currentMoveString[2] - AAA;
11726 toY = currentMoveString[3] - ONE;
11730 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11732 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11733 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11734 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11735 if(appData.findMirror) {
11736 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11737 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11742 /* Load the nth game from open file f */
11744 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11748 int gn = gameNumber;
11749 ListGame *lg = NULL;
11750 int numPGNTags = 0;
11752 GameMode oldGameMode;
11753 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11755 if (appData.debugMode)
11756 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11758 if (gameMode == Training )
11759 SetTrainingModeOff();
11761 oldGameMode = gameMode;
11762 if (gameMode != BeginningOfGame) {
11763 Reset(FALSE, TRUE);
11767 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11768 fclose(lastLoadGameFP);
11772 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11775 fseek(f, lg->offset, 0);
11776 GameListHighlight(gameNumber);
11777 pos = lg->position;
11781 DisplayError(_("Game number out of range"), 0);
11786 if (fseek(f, 0, 0) == -1) {
11787 if (f == lastLoadGameFP ?
11788 gameNumber == lastLoadGameNumber + 1 :
11792 DisplayError(_("Can't seek on game file"), 0);
11797 lastLoadGameFP = f;
11798 lastLoadGameNumber = gameNumber;
11799 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11800 lastLoadGameUseList = useList;
11804 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11805 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11806 lg->gameInfo.black);
11808 } else if (*title != NULLCHAR) {
11809 if (gameNumber > 1) {
11810 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11813 DisplayTitle(title);
11817 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11818 gameMode = PlayFromGameFile;
11822 currentMove = forwardMostMove = backwardMostMove = 0;
11823 CopyBoard(boards[0], initialPosition);
11827 * Skip the first gn-1 games in the file.
11828 * Also skip over anything that precedes an identifiable
11829 * start of game marker, to avoid being confused by
11830 * garbage at the start of the file. Currently
11831 * recognized start of game markers are the move number "1",
11832 * the pattern "gnuchess .* game", the pattern
11833 * "^[#;%] [^ ]* game file", and a PGN tag block.
11834 * A game that starts with one of the latter two patterns
11835 * will also have a move number 1, possibly
11836 * following a position diagram.
11837 * 5-4-02: Let's try being more lenient and allowing a game to
11838 * start with an unnumbered move. Does that break anything?
11840 cm = lastLoadGameStart = EndOfFile;
11842 yyboardindex = forwardMostMove;
11843 cm = (ChessMove) Myylex();
11846 if (cmailMsgLoaded) {
11847 nCmailGames = CMAIL_MAX_GAMES - gn;
11850 DisplayError(_("Game not found in file"), 0);
11857 lastLoadGameStart = cm;
11860 case MoveNumberOne:
11861 switch (lastLoadGameStart) {
11866 case MoveNumberOne:
11868 gn--; /* count this game */
11869 lastLoadGameStart = cm;
11878 switch (lastLoadGameStart) {
11881 case MoveNumberOne:
11883 gn--; /* count this game */
11884 lastLoadGameStart = cm;
11887 lastLoadGameStart = cm; /* game counted already */
11895 yyboardindex = forwardMostMove;
11896 cm = (ChessMove) Myylex();
11897 } while (cm == PGNTag || cm == Comment);
11904 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11905 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11906 != CMAIL_OLD_RESULT) {
11908 cmailResult[ CMAIL_MAX_GAMES
11909 - gn - 1] = CMAIL_OLD_RESULT;
11915 /* Only a NormalMove can be at the start of a game
11916 * without a position diagram. */
11917 if (lastLoadGameStart == EndOfFile ) {
11919 lastLoadGameStart = MoveNumberOne;
11928 if (appData.debugMode)
11929 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11931 if (cm == XBoardGame) {
11932 /* Skip any header junk before position diagram and/or move 1 */
11934 yyboardindex = forwardMostMove;
11935 cm = (ChessMove) Myylex();
11937 if (cm == EndOfFile ||
11938 cm == GNUChessGame || cm == XBoardGame) {
11939 /* Empty game; pretend end-of-file and handle later */
11944 if (cm == MoveNumberOne || cm == PositionDiagram ||
11945 cm == PGNTag || cm == Comment)
11948 } else if (cm == GNUChessGame) {
11949 if (gameInfo.event != NULL) {
11950 free(gameInfo.event);
11952 gameInfo.event = StrSave(yy_text);
11955 startedFromSetupPosition = FALSE;
11956 while (cm == PGNTag) {
11957 if (appData.debugMode)
11958 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11959 err = ParsePGNTag(yy_text, &gameInfo);
11960 if (!err) numPGNTags++;
11962 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11963 if(gameInfo.variant != oldVariant) {
11964 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11965 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11966 InitPosition(TRUE);
11967 oldVariant = gameInfo.variant;
11968 if (appData.debugMode)
11969 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11973 if (gameInfo.fen != NULL) {
11974 Board initial_position;
11975 startedFromSetupPosition = TRUE;
11976 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11978 DisplayError(_("Bad FEN position in file"), 0);
11981 CopyBoard(boards[0], initial_position);
11982 if (blackPlaysFirst) {
11983 currentMove = forwardMostMove = backwardMostMove = 1;
11984 CopyBoard(boards[1], initial_position);
11985 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11986 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11987 timeRemaining[0][1] = whiteTimeRemaining;
11988 timeRemaining[1][1] = blackTimeRemaining;
11989 if (commentList[0] != NULL) {
11990 commentList[1] = commentList[0];
11991 commentList[0] = NULL;
11994 currentMove = forwardMostMove = backwardMostMove = 0;
11996 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11998 initialRulePlies = FENrulePlies;
11999 for( i=0; i< nrCastlingRights; i++ )
12000 initialRights[i] = initial_position[CASTLING][i];
12002 yyboardindex = forwardMostMove;
12003 free(gameInfo.fen);
12004 gameInfo.fen = NULL;
12007 yyboardindex = forwardMostMove;
12008 cm = (ChessMove) Myylex();
12010 /* Handle comments interspersed among the tags */
12011 while (cm == Comment) {
12013 if (appData.debugMode)
12014 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12016 AppendComment(currentMove, p, FALSE);
12017 yyboardindex = forwardMostMove;
12018 cm = (ChessMove) Myylex();
12022 /* don't rely on existence of Event tag since if game was
12023 * pasted from clipboard the Event tag may not exist
12025 if (numPGNTags > 0){
12027 if (gameInfo.variant == VariantNormal) {
12028 VariantClass v = StringToVariant(gameInfo.event);
12029 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12030 if(v < VariantShogi) gameInfo.variant = v;
12033 if( appData.autoDisplayTags ) {
12034 tags = PGNTags(&gameInfo);
12035 TagsPopUp(tags, CmailMsg());
12040 /* Make something up, but don't display it now */
12045 if (cm == PositionDiagram) {
12048 Board initial_position;
12050 if (appData.debugMode)
12051 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12053 if (!startedFromSetupPosition) {
12055 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12056 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12067 initial_position[i][j++] = CharToPiece(*p);
12070 while (*p == ' ' || *p == '\t' ||
12071 *p == '\n' || *p == '\r') p++;
12073 if (strncmp(p, "black", strlen("black"))==0)
12074 blackPlaysFirst = TRUE;
12076 blackPlaysFirst = FALSE;
12077 startedFromSetupPosition = TRUE;
12079 CopyBoard(boards[0], initial_position);
12080 if (blackPlaysFirst) {
12081 currentMove = forwardMostMove = backwardMostMove = 1;
12082 CopyBoard(boards[1], initial_position);
12083 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12084 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12085 timeRemaining[0][1] = whiteTimeRemaining;
12086 timeRemaining[1][1] = blackTimeRemaining;
12087 if (commentList[0] != NULL) {
12088 commentList[1] = commentList[0];
12089 commentList[0] = NULL;
12092 currentMove = forwardMostMove = backwardMostMove = 0;
12095 yyboardindex = forwardMostMove;
12096 cm = (ChessMove) Myylex();
12099 if (first.pr == NoProc) {
12100 StartChessProgram(&first);
12102 InitChessProgram(&first, FALSE);
12103 SendToProgram("force\n", &first);
12104 if (startedFromSetupPosition) {
12105 SendBoard(&first, forwardMostMove);
12106 if (appData.debugMode) {
12107 fprintf(debugFP, "Load Game\n");
12109 DisplayBothClocks();
12112 /* [HGM] server: flag to write setup moves in broadcast file as one */
12113 loadFlag = appData.suppressLoadMoves;
12115 while (cm == Comment) {
12117 if (appData.debugMode)
12118 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12120 AppendComment(currentMove, p, FALSE);
12121 yyboardindex = forwardMostMove;
12122 cm = (ChessMove) Myylex();
12125 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12126 cm == WhiteWins || cm == BlackWins ||
12127 cm == GameIsDrawn || cm == GameUnfinished) {
12128 DisplayMessage("", _("No moves in game"));
12129 if (cmailMsgLoaded) {
12130 if (appData.debugMode)
12131 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12135 DrawPosition(FALSE, boards[currentMove]);
12136 DisplayBothClocks();
12137 gameMode = EditGame;
12144 // [HGM] PV info: routine tests if comment empty
12145 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12146 DisplayComment(currentMove - 1, commentList[currentMove]);
12148 if (!matchMode && appData.timeDelay != 0)
12149 DrawPosition(FALSE, boards[currentMove]);
12151 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12152 programStats.ok_to_send = 1;
12155 /* if the first token after the PGN tags is a move
12156 * and not move number 1, retrieve it from the parser
12158 if (cm != MoveNumberOne)
12159 LoadGameOneMove(cm);
12161 /* load the remaining moves from the file */
12162 while (LoadGameOneMove(EndOfFile)) {
12163 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12164 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12167 /* rewind to the start of the game */
12168 currentMove = backwardMostMove;
12170 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12172 if (oldGameMode == AnalyzeFile ||
12173 oldGameMode == AnalyzeMode) {
12174 AnalyzeFileEvent();
12177 if (!matchMode && pos > 0) {
12178 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12180 if (matchMode || appData.timeDelay == 0) {
12182 } else if (appData.timeDelay > 0) {
12183 AutoPlayGameLoop();
12186 if (appData.debugMode)
12187 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12189 loadFlag = 0; /* [HGM] true game starts */
12193 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12195 ReloadPosition (int offset)
12197 int positionNumber = lastLoadPositionNumber + offset;
12198 if (lastLoadPositionFP == NULL) {
12199 DisplayError(_("No position has been loaded yet"), 0);
12202 if (positionNumber <= 0) {
12203 DisplayError(_("Can't back up any further"), 0);
12206 return LoadPosition(lastLoadPositionFP, positionNumber,
12207 lastLoadPositionTitle);
12210 /* Load the nth position from the given file */
12212 LoadPositionFromFile (char *filename, int n, char *title)
12217 if (strcmp(filename, "-") == 0) {
12218 return LoadPosition(stdin, n, "stdin");
12220 f = fopen(filename, "rb");
12222 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12223 DisplayError(buf, errno);
12226 return LoadPosition(f, n, title);
12231 /* Load the nth position from the given open file, and close it */
12233 LoadPosition (FILE *f, int positionNumber, char *title)
12235 char *p, line[MSG_SIZ];
12236 Board initial_position;
12237 int i, j, fenMode, pn;
12239 if (gameMode == Training )
12240 SetTrainingModeOff();
12242 if (gameMode != BeginningOfGame) {
12243 Reset(FALSE, TRUE);
12245 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12246 fclose(lastLoadPositionFP);
12248 if (positionNumber == 0) positionNumber = 1;
12249 lastLoadPositionFP = f;
12250 lastLoadPositionNumber = positionNumber;
12251 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12252 if (first.pr == NoProc && !appData.noChessProgram) {
12253 StartChessProgram(&first);
12254 InitChessProgram(&first, FALSE);
12256 pn = positionNumber;
12257 if (positionNumber < 0) {
12258 /* Negative position number means to seek to that byte offset */
12259 if (fseek(f, -positionNumber, 0) == -1) {
12260 DisplayError(_("Can't seek on position file"), 0);
12265 if (fseek(f, 0, 0) == -1) {
12266 if (f == lastLoadPositionFP ?
12267 positionNumber == lastLoadPositionNumber + 1 :
12268 positionNumber == 1) {
12271 DisplayError(_("Can't seek on position file"), 0);
12276 /* See if this file is FEN or old-style xboard */
12277 if (fgets(line, MSG_SIZ, f) == NULL) {
12278 DisplayError(_("Position not found in file"), 0);
12281 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12282 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12285 if (fenMode || line[0] == '#') pn--;
12287 /* skip positions before number pn */
12288 if (fgets(line, MSG_SIZ, f) == NULL) {
12290 DisplayError(_("Position not found in file"), 0);
12293 if (fenMode || line[0] == '#') pn--;
12298 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12299 DisplayError(_("Bad FEN position in file"), 0);
12303 (void) fgets(line, MSG_SIZ, f);
12304 (void) fgets(line, MSG_SIZ, f);
12306 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12307 (void) fgets(line, MSG_SIZ, f);
12308 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12311 initial_position[i][j++] = CharToPiece(*p);
12315 blackPlaysFirst = FALSE;
12317 (void) fgets(line, MSG_SIZ, f);
12318 if (strncmp(line, "black", strlen("black"))==0)
12319 blackPlaysFirst = TRUE;
12322 startedFromSetupPosition = TRUE;
12324 CopyBoard(boards[0], initial_position);
12325 if (blackPlaysFirst) {
12326 currentMove = forwardMostMove = backwardMostMove = 1;
12327 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12328 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12329 CopyBoard(boards[1], initial_position);
12330 DisplayMessage("", _("Black to play"));
12332 currentMove = forwardMostMove = backwardMostMove = 0;
12333 DisplayMessage("", _("White to play"));
12335 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12336 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12337 SendToProgram("force\n", &first);
12338 SendBoard(&first, forwardMostMove);
12340 if (appData.debugMode) {
12342 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12343 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12344 fprintf(debugFP, "Load Position\n");
12347 if (positionNumber > 1) {
12348 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12349 DisplayTitle(line);
12351 DisplayTitle(title);
12353 gameMode = EditGame;
12356 timeRemaining[0][1] = whiteTimeRemaining;
12357 timeRemaining[1][1] = blackTimeRemaining;
12358 DrawPosition(FALSE, boards[currentMove]);
12365 CopyPlayerNameIntoFileName (char **dest, char *src)
12367 while (*src != NULLCHAR && *src != ',') {
12372 *(*dest)++ = *src++;
12378 DefaultFileName (char *ext)
12380 static char def[MSG_SIZ];
12383 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12385 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12387 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12389 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12396 /* Save the current game to the given file */
12398 SaveGameToFile (char *filename, int append)
12402 int result, i, t,tot=0;
12404 if (strcmp(filename, "-") == 0) {
12405 return SaveGame(stdout, 0, NULL);
12407 for(i=0; i<10; i++) { // upto 10 tries
12408 f = fopen(filename, append ? "a" : "w");
12409 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12410 if(f || errno != 13) break;
12411 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12415 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12416 DisplayError(buf, errno);
12419 safeStrCpy(buf, lastMsg, MSG_SIZ);
12420 DisplayMessage(_("Waiting for access to save file"), "");
12421 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12422 DisplayMessage(_("Saving game"), "");
12423 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12424 result = SaveGame(f, 0, NULL);
12425 DisplayMessage(buf, "");
12432 SavePart (char *str)
12434 static char buf[MSG_SIZ];
12437 p = strchr(str, ' ');
12438 if (p == NULL) return str;
12439 strncpy(buf, str, p - str);
12440 buf[p - str] = NULLCHAR;
12444 #define PGN_MAX_LINE 75
12446 #define PGN_SIDE_WHITE 0
12447 #define PGN_SIDE_BLACK 1
12450 FindFirstMoveOutOfBook (int side)
12454 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12455 int index = backwardMostMove;
12456 int has_book_hit = 0;
12458 if( (index % 2) != side ) {
12462 while( index < forwardMostMove ) {
12463 /* Check to see if engine is in book */
12464 int depth = pvInfoList[index].depth;
12465 int score = pvInfoList[index].score;
12471 else if( score == 0 && depth == 63 ) {
12472 in_book = 1; /* Zappa */
12474 else if( score == 2 && depth == 99 ) {
12475 in_book = 1; /* Abrok */
12478 has_book_hit += in_book;
12494 GetOutOfBookInfo (char * buf)
12498 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12500 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12501 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12505 if( oob[0] >= 0 || oob[1] >= 0 ) {
12506 for( i=0; i<2; i++ ) {
12510 if( i > 0 && oob[0] >= 0 ) {
12511 strcat( buf, " " );
12514 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12515 sprintf( buf+strlen(buf), "%s%.2f",
12516 pvInfoList[idx].score >= 0 ? "+" : "",
12517 pvInfoList[idx].score / 100.0 );
12523 /* Save game in PGN style and close the file */
12525 SaveGamePGN (FILE *f)
12527 int i, offset, linelen, newblock;
12531 int movelen, numlen, blank;
12532 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12534 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12536 tm = time((time_t *) NULL);
12538 PrintPGNTags(f, &gameInfo);
12540 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12542 if (backwardMostMove > 0 || startedFromSetupPosition) {
12543 char *fen = PositionToFEN(backwardMostMove, NULL);
12544 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12545 fprintf(f, "\n{--------------\n");
12546 PrintPosition(f, backwardMostMove);
12547 fprintf(f, "--------------}\n");
12551 /* [AS] Out of book annotation */
12552 if( appData.saveOutOfBookInfo ) {
12555 GetOutOfBookInfo( buf );
12557 if( buf[0] != '\0' ) {
12558 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12565 i = backwardMostMove;
12569 while (i < forwardMostMove) {
12570 /* Print comments preceding this move */
12571 if (commentList[i] != NULL) {
12572 if (linelen > 0) fprintf(f, "\n");
12573 fprintf(f, "%s", commentList[i]);
12578 /* Format move number */
12580 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12583 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12585 numtext[0] = NULLCHAR;
12587 numlen = strlen(numtext);
12590 /* Print move number */
12591 blank = linelen > 0 && numlen > 0;
12592 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12601 fprintf(f, "%s", numtext);
12605 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12606 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12609 blank = linelen > 0 && movelen > 0;
12610 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12619 fprintf(f, "%s", move_buffer);
12620 linelen += movelen;
12622 /* [AS] Add PV info if present */
12623 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12624 /* [HGM] add time */
12625 char buf[MSG_SIZ]; int seconds;
12627 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12633 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12636 seconds = (seconds + 4)/10; // round to full seconds
12638 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12640 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12643 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12644 pvInfoList[i].score >= 0 ? "+" : "",
12645 pvInfoList[i].score / 100.0,
12646 pvInfoList[i].depth,
12649 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12651 /* Print score/depth */
12652 blank = linelen > 0 && movelen > 0;
12653 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12662 fprintf(f, "%s", move_buffer);
12663 linelen += movelen;
12669 /* Start a new line */
12670 if (linelen > 0) fprintf(f, "\n");
12672 /* Print comments after last move */
12673 if (commentList[i] != NULL) {
12674 fprintf(f, "%s\n", commentList[i]);
12678 if (gameInfo.resultDetails != NULL &&
12679 gameInfo.resultDetails[0] != NULLCHAR) {
12680 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12681 PGNResult(gameInfo.result));
12683 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12687 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12691 /* Save game in old style and close the file */
12693 SaveGameOldStyle (FILE *f)
12698 tm = time((time_t *) NULL);
12700 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12703 if (backwardMostMove > 0 || startedFromSetupPosition) {
12704 fprintf(f, "\n[--------------\n");
12705 PrintPosition(f, backwardMostMove);
12706 fprintf(f, "--------------]\n");
12711 i = backwardMostMove;
12712 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12714 while (i < forwardMostMove) {
12715 if (commentList[i] != NULL) {
12716 fprintf(f, "[%s]\n", commentList[i]);
12719 if ((i % 2) == 1) {
12720 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12723 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12725 if (commentList[i] != NULL) {
12729 if (i >= forwardMostMove) {
12733 fprintf(f, "%s\n", parseList[i]);
12738 if (commentList[i] != NULL) {
12739 fprintf(f, "[%s]\n", commentList[i]);
12742 /* This isn't really the old style, but it's close enough */
12743 if (gameInfo.resultDetails != NULL &&
12744 gameInfo.resultDetails[0] != NULLCHAR) {
12745 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12746 gameInfo.resultDetails);
12748 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12755 /* Save the current game to open file f and close the file */
12757 SaveGame (FILE *f, int dummy, char *dummy2)
12759 if (gameMode == EditPosition) EditPositionDone(TRUE);
12760 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12761 if (appData.oldSaveStyle)
12762 return SaveGameOldStyle(f);
12764 return SaveGamePGN(f);
12767 /* Save the current position to the given file */
12769 SavePositionToFile (char *filename)
12774 if (strcmp(filename, "-") == 0) {
12775 return SavePosition(stdout, 0, NULL);
12777 f = fopen(filename, "a");
12779 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12780 DisplayError(buf, errno);
12783 safeStrCpy(buf, lastMsg, MSG_SIZ);
12784 DisplayMessage(_("Waiting for access to save file"), "");
12785 flock(fileno(f), LOCK_EX); // [HGM] lock
12786 DisplayMessage(_("Saving position"), "");
12787 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12788 SavePosition(f, 0, NULL);
12789 DisplayMessage(buf, "");
12795 /* Save the current position to the given open file and close the file */
12797 SavePosition (FILE *f, int dummy, char *dummy2)
12802 if (gameMode == EditPosition) EditPositionDone(TRUE);
12803 if (appData.oldSaveStyle) {
12804 tm = time((time_t *) NULL);
12806 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12808 fprintf(f, "[--------------\n");
12809 PrintPosition(f, currentMove);
12810 fprintf(f, "--------------]\n");
12812 fen = PositionToFEN(currentMove, NULL);
12813 fprintf(f, "%s\n", fen);
12821 ReloadCmailMsgEvent (int unregister)
12824 static char *inFilename = NULL;
12825 static char *outFilename;
12827 struct stat inbuf, outbuf;
12830 /* Any registered moves are unregistered if unregister is set, */
12831 /* i.e. invoked by the signal handler */
12833 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12834 cmailMoveRegistered[i] = FALSE;
12835 if (cmailCommentList[i] != NULL) {
12836 free(cmailCommentList[i]);
12837 cmailCommentList[i] = NULL;
12840 nCmailMovesRegistered = 0;
12843 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12844 cmailResult[i] = CMAIL_NOT_RESULT;
12848 if (inFilename == NULL) {
12849 /* Because the filenames are static they only get malloced once */
12850 /* and they never get freed */
12851 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12852 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12854 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12855 sprintf(outFilename, "%s.out", appData.cmailGameName);
12858 status = stat(outFilename, &outbuf);
12860 cmailMailedMove = FALSE;
12862 status = stat(inFilename, &inbuf);
12863 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12866 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12867 counts the games, notes how each one terminated, etc.
12869 It would be nice to remove this kludge and instead gather all
12870 the information while building the game list. (And to keep it
12871 in the game list nodes instead of having a bunch of fixed-size
12872 parallel arrays.) Note this will require getting each game's
12873 termination from the PGN tags, as the game list builder does
12874 not process the game moves. --mann
12876 cmailMsgLoaded = TRUE;
12877 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12879 /* Load first game in the file or popup game menu */
12880 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12882 #endif /* !WIN32 */
12890 char string[MSG_SIZ];
12892 if ( cmailMailedMove
12893 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12894 return TRUE; /* Allow free viewing */
12897 /* Unregister move to ensure that we don't leave RegisterMove */
12898 /* with the move registered when the conditions for registering no */
12900 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12901 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12902 nCmailMovesRegistered --;
12904 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12906 free(cmailCommentList[lastLoadGameNumber - 1]);
12907 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12911 if (cmailOldMove == -1) {
12912 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12916 if (currentMove > cmailOldMove + 1) {
12917 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12921 if (currentMove < cmailOldMove) {
12922 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12926 if (forwardMostMove > currentMove) {
12927 /* Silently truncate extra moves */
12931 if ( (currentMove == cmailOldMove + 1)
12932 || ( (currentMove == cmailOldMove)
12933 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12934 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12935 if (gameInfo.result != GameUnfinished) {
12936 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12939 if (commentList[currentMove] != NULL) {
12940 cmailCommentList[lastLoadGameNumber - 1]
12941 = StrSave(commentList[currentMove]);
12943 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12945 if (appData.debugMode)
12946 fprintf(debugFP, "Saving %s for game %d\n",
12947 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12949 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12951 f = fopen(string, "w");
12952 if (appData.oldSaveStyle) {
12953 SaveGameOldStyle(f); /* also closes the file */
12955 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12956 f = fopen(string, "w");
12957 SavePosition(f, 0, NULL); /* also closes the file */
12959 fprintf(f, "{--------------\n");
12960 PrintPosition(f, currentMove);
12961 fprintf(f, "--------------}\n\n");
12963 SaveGame(f, 0, NULL); /* also closes the file*/
12966 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12967 nCmailMovesRegistered ++;
12968 } else if (nCmailGames == 1) {
12969 DisplayError(_("You have not made a move yet"), 0);
12980 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12981 FILE *commandOutput;
12982 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12983 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12989 if (! cmailMsgLoaded) {
12990 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12994 if (nCmailGames == nCmailResults) {
12995 DisplayError(_("No unfinished games"), 0);
12999 #if CMAIL_PROHIBIT_REMAIL
13000 if (cmailMailedMove) {
13001 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);
13002 DisplayError(msg, 0);
13007 if (! (cmailMailedMove || RegisterMove())) return;
13009 if ( cmailMailedMove
13010 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13011 snprintf(string, MSG_SIZ, partCommandString,
13012 appData.debugMode ? " -v" : "", appData.cmailGameName);
13013 commandOutput = popen(string, "r");
13015 if (commandOutput == NULL) {
13016 DisplayError(_("Failed to invoke cmail"), 0);
13018 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13019 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13021 if (nBuffers > 1) {
13022 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13023 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13024 nBytes = MSG_SIZ - 1;
13026 (void) memcpy(msg, buffer, nBytes);
13028 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13030 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13031 cmailMailedMove = TRUE; /* Prevent >1 moves */
13034 for (i = 0; i < nCmailGames; i ++) {
13035 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13040 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13042 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13044 appData.cmailGameName,
13046 LoadGameFromFile(buffer, 1, buffer, FALSE);
13047 cmailMsgLoaded = FALSE;
13051 DisplayInformation(msg);
13052 pclose(commandOutput);
13055 if ((*cmailMsg) != '\0') {
13056 DisplayInformation(cmailMsg);
13061 #endif /* !WIN32 */
13070 int prependComma = 0;
13072 char string[MSG_SIZ]; /* Space for game-list */
13075 if (!cmailMsgLoaded) return "";
13077 if (cmailMailedMove) {
13078 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13080 /* Create a list of games left */
13081 snprintf(string, MSG_SIZ, "[");
13082 for (i = 0; i < nCmailGames; i ++) {
13083 if (! ( cmailMoveRegistered[i]
13084 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13085 if (prependComma) {
13086 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13088 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13092 strcat(string, number);
13095 strcat(string, "]");
13097 if (nCmailMovesRegistered + nCmailResults == 0) {
13098 switch (nCmailGames) {
13100 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13104 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13108 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13113 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13115 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13120 if (nCmailResults == nCmailGames) {
13121 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13123 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13128 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13140 if (gameMode == Training)
13141 SetTrainingModeOff();
13144 cmailMsgLoaded = FALSE;
13145 if (appData.icsActive) {
13146 SendToICS(ics_prefix);
13147 SendToICS("refresh\n");
13152 ExitEvent (int status)
13156 /* Give up on clean exit */
13160 /* Keep trying for clean exit */
13164 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13166 if (telnetISR != NULL) {
13167 RemoveInputSource(telnetISR);
13169 if (icsPR != NoProc) {
13170 DestroyChildProcess(icsPR, TRUE);
13173 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13174 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13176 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13177 /* make sure this other one finishes before killing it! */
13178 if(endingGame) { int count = 0;
13179 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13180 while(endingGame && count++ < 10) DoSleep(1);
13181 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13184 /* Kill off chess programs */
13185 if (first.pr != NoProc) {
13188 DoSleep( appData.delayBeforeQuit );
13189 SendToProgram("quit\n", &first);
13190 DoSleep( appData.delayAfterQuit );
13191 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13193 if (second.pr != NoProc) {
13194 DoSleep( appData.delayBeforeQuit );
13195 SendToProgram("quit\n", &second);
13196 DoSleep( appData.delayAfterQuit );
13197 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13199 if (first.isr != NULL) {
13200 RemoveInputSource(first.isr);
13202 if (second.isr != NULL) {
13203 RemoveInputSource(second.isr);
13206 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13207 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13209 ShutDownFrontEnd();
13216 if (appData.debugMode)
13217 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13221 if (gameMode == MachinePlaysWhite ||
13222 gameMode == MachinePlaysBlack) {
13225 DisplayBothClocks();
13227 if (gameMode == PlayFromGameFile) {
13228 if (appData.timeDelay >= 0)
13229 AutoPlayGameLoop();
13230 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13231 Reset(FALSE, TRUE);
13232 SendToICS(ics_prefix);
13233 SendToICS("refresh\n");
13234 } else if (currentMove < forwardMostMove) {
13235 ForwardInner(forwardMostMove);
13237 pauseExamInvalid = FALSE;
13239 switch (gameMode) {
13243 pauseExamForwardMostMove = forwardMostMove;
13244 pauseExamInvalid = FALSE;
13247 case IcsPlayingWhite:
13248 case IcsPlayingBlack:
13252 case PlayFromGameFile:
13253 (void) StopLoadGameTimer();
13257 case BeginningOfGame:
13258 if (appData.icsActive) return;
13259 /* else fall through */
13260 case MachinePlaysWhite:
13261 case MachinePlaysBlack:
13262 case TwoMachinesPlay:
13263 if (forwardMostMove == 0)
13264 return; /* don't pause if no one has moved */
13265 if ((gameMode == MachinePlaysWhite &&
13266 !WhiteOnMove(forwardMostMove)) ||
13267 (gameMode == MachinePlaysBlack &&
13268 WhiteOnMove(forwardMostMove))) {
13279 EditCommentEvent ()
13281 char title[MSG_SIZ];
13283 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13284 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13286 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13287 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13288 parseList[currentMove - 1]);
13291 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13298 char *tags = PGNTags(&gameInfo);
13300 EditTagsPopUp(tags, NULL);
13305 AnalyzeModeEvent ()
13307 if (appData.noChessProgram || gameMode == AnalyzeMode)
13310 if (gameMode != AnalyzeFile) {
13311 if (!appData.icsEngineAnalyze) {
13313 if (gameMode != EditGame) return;
13315 ResurrectChessProgram();
13316 SendToProgram("analyze\n", &first);
13317 first.analyzing = TRUE;
13318 /*first.maybeThinking = TRUE;*/
13319 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13320 EngineOutputPopUp();
13322 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13327 StartAnalysisClock();
13328 GetTimeMark(&lastNodeCountTime);
13333 AnalyzeFileEvent ()
13335 if (appData.noChessProgram || gameMode == AnalyzeFile)
13338 if (gameMode != AnalyzeMode) {
13340 if (gameMode != EditGame) return;
13341 ResurrectChessProgram();
13342 SendToProgram("analyze\n", &first);
13343 first.analyzing = TRUE;
13344 /*first.maybeThinking = TRUE;*/
13345 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13346 EngineOutputPopUp();
13348 gameMode = AnalyzeFile;
13353 StartAnalysisClock();
13354 GetTimeMark(&lastNodeCountTime);
13356 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13360 MachineWhiteEvent ()
13363 char *bookHit = NULL;
13365 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13369 if (gameMode == PlayFromGameFile ||
13370 gameMode == TwoMachinesPlay ||
13371 gameMode == Training ||
13372 gameMode == AnalyzeMode ||
13373 gameMode == EndOfGame)
13376 if (gameMode == EditPosition)
13377 EditPositionDone(TRUE);
13379 if (!WhiteOnMove(currentMove)) {
13380 DisplayError(_("It is not White's turn"), 0);
13384 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13387 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13388 gameMode == AnalyzeFile)
13391 ResurrectChessProgram(); /* in case it isn't running */
13392 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13393 gameMode = MachinePlaysWhite;
13396 gameMode = MachinePlaysWhite;
13400 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13402 if (first.sendName) {
13403 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13404 SendToProgram(buf, &first);
13406 if (first.sendTime) {
13407 if (first.useColors) {
13408 SendToProgram("black\n", &first); /*gnu kludge*/
13410 SendTimeRemaining(&first, TRUE);
13412 if (first.useColors) {
13413 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13415 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13416 SetMachineThinkingEnables();
13417 first.maybeThinking = TRUE;
13421 if (appData.autoFlipView && !flipView) {
13422 flipView = !flipView;
13423 DrawPosition(FALSE, NULL);
13424 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13427 if(bookHit) { // [HGM] book: simulate book reply
13428 static char bookMove[MSG_SIZ]; // a bit generous?
13430 programStats.nodes = programStats.depth = programStats.time =
13431 programStats.score = programStats.got_only_move = 0;
13432 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13434 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13435 strcat(bookMove, bookHit);
13436 HandleMachineMove(bookMove, &first);
13441 MachineBlackEvent ()
13444 char *bookHit = NULL;
13446 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13450 if (gameMode == PlayFromGameFile ||
13451 gameMode == TwoMachinesPlay ||
13452 gameMode == Training ||
13453 gameMode == AnalyzeMode ||
13454 gameMode == EndOfGame)
13457 if (gameMode == EditPosition)
13458 EditPositionDone(TRUE);
13460 if (WhiteOnMove(currentMove)) {
13461 DisplayError(_("It is not Black's turn"), 0);
13465 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13468 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13469 gameMode == AnalyzeFile)
13472 ResurrectChessProgram(); /* in case it isn't running */
13473 gameMode = MachinePlaysBlack;
13477 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13479 if (first.sendName) {
13480 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13481 SendToProgram(buf, &first);
13483 if (first.sendTime) {
13484 if (first.useColors) {
13485 SendToProgram("white\n", &first); /*gnu kludge*/
13487 SendTimeRemaining(&first, FALSE);
13489 if (first.useColors) {
13490 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13492 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13493 SetMachineThinkingEnables();
13494 first.maybeThinking = TRUE;
13497 if (appData.autoFlipView && flipView) {
13498 flipView = !flipView;
13499 DrawPosition(FALSE, NULL);
13500 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13502 if(bookHit) { // [HGM] book: simulate book reply
13503 static char bookMove[MSG_SIZ]; // a bit generous?
13505 programStats.nodes = programStats.depth = programStats.time =
13506 programStats.score = programStats.got_only_move = 0;
13507 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13509 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13510 strcat(bookMove, bookHit);
13511 HandleMachineMove(bookMove, &first);
13517 DisplayTwoMachinesTitle ()
13520 if (appData.matchGames > 0) {
13521 if(appData.tourneyFile[0]) {
13522 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13523 gameInfo.white, _("vs."), gameInfo.black,
13524 nextGame+1, appData.matchGames+1,
13525 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13527 if (first.twoMachinesColor[0] == 'w') {
13528 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13529 gameInfo.white, _("vs."), gameInfo.black,
13530 first.matchWins, second.matchWins,
13531 matchGame - 1 - (first.matchWins + second.matchWins));
13533 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13534 gameInfo.white, _("vs."), gameInfo.black,
13535 second.matchWins, first.matchWins,
13536 matchGame - 1 - (first.matchWins + second.matchWins));
13539 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13545 SettingsMenuIfReady ()
13547 if (second.lastPing != second.lastPong) {
13548 DisplayMessage("", _("Waiting for second chess program"));
13549 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13553 DisplayMessage("", "");
13554 SettingsPopUp(&second);
13558 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13561 if (cps->pr == NoProc) {
13562 StartChessProgram(cps);
13563 if (cps->protocolVersion == 1) {
13566 /* kludge: allow timeout for initial "feature" command */
13568 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13569 DisplayMessage("", buf);
13570 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13578 TwoMachinesEvent P((void))
13582 ChessProgramState *onmove;
13583 char *bookHit = NULL;
13584 static int stalling = 0;
13588 if (appData.noChessProgram) return;
13590 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13591 DisplayError("second engine does not play this", 0);
13595 switch (gameMode) {
13596 case TwoMachinesPlay:
13598 case MachinePlaysWhite:
13599 case MachinePlaysBlack:
13600 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13601 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13605 case BeginningOfGame:
13606 case PlayFromGameFile:
13609 if (gameMode != EditGame) return;
13612 EditPositionDone(TRUE);
13623 // forwardMostMove = currentMove;
13624 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13626 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13628 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13629 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13630 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13634 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13635 SendToProgram("force\n", &second);
13637 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13640 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13641 if(appData.matchPause>10000 || appData.matchPause<10)
13642 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13643 wait = SubtractTimeMarks(&now, &pauseStart);
13644 if(wait < appData.matchPause) {
13645 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13648 // we are now committed to starting the game
13650 DisplayMessage("", "");
13651 if (startedFromSetupPosition) {
13652 SendBoard(&second, backwardMostMove);
13653 if (appData.debugMode) {
13654 fprintf(debugFP, "Two Machines\n");
13657 for (i = backwardMostMove; i < forwardMostMove; i++) {
13658 SendMoveToProgram(i, &second);
13661 gameMode = TwoMachinesPlay;
13663 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13665 DisplayTwoMachinesTitle();
13667 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13672 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13673 SendToProgram(first.computerString, &first);
13674 if (first.sendName) {
13675 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13676 SendToProgram(buf, &first);
13678 SendToProgram(second.computerString, &second);
13679 if (second.sendName) {
13680 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13681 SendToProgram(buf, &second);
13685 if (!first.sendTime || !second.sendTime) {
13686 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13687 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13689 if (onmove->sendTime) {
13690 if (onmove->useColors) {
13691 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13693 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13695 if (onmove->useColors) {
13696 SendToProgram(onmove->twoMachinesColor, onmove);
13698 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13699 // SendToProgram("go\n", onmove);
13700 onmove->maybeThinking = TRUE;
13701 SetMachineThinkingEnables();
13705 if(bookHit) { // [HGM] book: simulate book reply
13706 static char bookMove[MSG_SIZ]; // a bit generous?
13708 programStats.nodes = programStats.depth = programStats.time =
13709 programStats.score = programStats.got_only_move = 0;
13710 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13712 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13713 strcat(bookMove, bookHit);
13714 savedMessage = bookMove; // args for deferred call
13715 savedState = onmove;
13716 ScheduleDelayedEvent(DeferredBookMove, 1);
13723 if (gameMode == Training) {
13724 SetTrainingModeOff();
13725 gameMode = PlayFromGameFile;
13726 DisplayMessage("", _("Training mode off"));
13728 gameMode = Training;
13729 animateTraining = appData.animate;
13731 /* make sure we are not already at the end of the game */
13732 if (currentMove < forwardMostMove) {
13733 SetTrainingModeOn();
13734 DisplayMessage("", _("Training mode on"));
13736 gameMode = PlayFromGameFile;
13737 DisplayError(_("Already at end of game"), 0);
13746 if (!appData.icsActive) return;
13747 switch (gameMode) {
13748 case IcsPlayingWhite:
13749 case IcsPlayingBlack:
13752 case BeginningOfGame:
13760 EditPositionDone(TRUE);
13773 gameMode = IcsIdle;
13783 switch (gameMode) {
13785 SetTrainingModeOff();
13787 case MachinePlaysWhite:
13788 case MachinePlaysBlack:
13789 case BeginningOfGame:
13790 SendToProgram("force\n", &first);
13791 SetUserThinkingEnables();
13793 case PlayFromGameFile:
13794 (void) StopLoadGameTimer();
13795 if (gameFileFP != NULL) {
13800 EditPositionDone(TRUE);
13805 SendToProgram("force\n", &first);
13807 case TwoMachinesPlay:
13808 GameEnds(EndOfFile, NULL, GE_PLAYER);
13809 ResurrectChessProgram();
13810 SetUserThinkingEnables();
13813 ResurrectChessProgram();
13815 case IcsPlayingBlack:
13816 case IcsPlayingWhite:
13817 DisplayError(_("Warning: You are still playing a game"), 0);
13820 DisplayError(_("Warning: You are still observing a game"), 0);
13823 DisplayError(_("Warning: You are still examining a game"), 0);
13834 first.offeredDraw = second.offeredDraw = 0;
13836 if (gameMode == PlayFromGameFile) {
13837 whiteTimeRemaining = timeRemaining[0][currentMove];
13838 blackTimeRemaining = timeRemaining[1][currentMove];
13842 if (gameMode == MachinePlaysWhite ||
13843 gameMode == MachinePlaysBlack ||
13844 gameMode == TwoMachinesPlay ||
13845 gameMode == EndOfGame) {
13846 i = forwardMostMove;
13847 while (i > currentMove) {
13848 SendToProgram("undo\n", &first);
13851 if(!adjustedClock) {
13852 whiteTimeRemaining = timeRemaining[0][currentMove];
13853 blackTimeRemaining = timeRemaining[1][currentMove];
13854 DisplayBothClocks();
13856 if (whiteFlag || blackFlag) {
13857 whiteFlag = blackFlag = 0;
13862 gameMode = EditGame;
13869 EditPositionEvent ()
13871 if (gameMode == EditPosition) {
13877 if (gameMode != EditGame) return;
13879 gameMode = EditPosition;
13882 if (currentMove > 0)
13883 CopyBoard(boards[0], boards[currentMove]);
13885 blackPlaysFirst = !WhiteOnMove(currentMove);
13887 currentMove = forwardMostMove = backwardMostMove = 0;
13888 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13890 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13896 /* [DM] icsEngineAnalyze - possible call from other functions */
13897 if (appData.icsEngineAnalyze) {
13898 appData.icsEngineAnalyze = FALSE;
13900 DisplayMessage("",_("Close ICS engine analyze..."));
13902 if (first.analysisSupport && first.analyzing) {
13903 SendToProgram("exit\n", &first);
13904 first.analyzing = FALSE;
13906 thinkOutput[0] = NULLCHAR;
13910 EditPositionDone (Boolean fakeRights)
13912 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13914 startedFromSetupPosition = TRUE;
13915 InitChessProgram(&first, FALSE);
13916 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13917 boards[0][EP_STATUS] = EP_NONE;
13918 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13919 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13920 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13921 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13922 } else boards[0][CASTLING][2] = NoRights;
13923 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13924 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13925 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13926 } else boards[0][CASTLING][5] = NoRights;
13928 SendToProgram("force\n", &first);
13929 if (blackPlaysFirst) {
13930 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13931 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13932 currentMove = forwardMostMove = backwardMostMove = 1;
13933 CopyBoard(boards[1], boards[0]);
13935 currentMove = forwardMostMove = backwardMostMove = 0;
13937 SendBoard(&first, forwardMostMove);
13938 if (appData.debugMode) {
13939 fprintf(debugFP, "EditPosDone\n");
13942 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13943 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13944 gameMode = EditGame;
13946 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13947 ClearHighlights(); /* [AS] */
13950 /* Pause for `ms' milliseconds */
13951 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13953 TimeDelay (long ms)
13960 } while (SubtractTimeMarks(&m2, &m1) < ms);
13963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13965 SendMultiLineToICS (char *buf)
13967 char temp[MSG_SIZ+1], *p;
13974 strncpy(temp, buf, len);
13979 if (*p == '\n' || *p == '\r')
13984 strcat(temp, "\n");
13986 SendToPlayer(temp, strlen(temp));
13990 SetWhiteToPlayEvent ()
13992 if (gameMode == EditPosition) {
13993 blackPlaysFirst = FALSE;
13994 DisplayBothClocks(); /* works because currentMove is 0 */
13995 } else if (gameMode == IcsExamining) {
13996 SendToICS(ics_prefix);
13997 SendToICS("tomove white\n");
14002 SetBlackToPlayEvent ()
14004 if (gameMode == EditPosition) {
14005 blackPlaysFirst = TRUE;
14006 currentMove = 1; /* kludge */
14007 DisplayBothClocks();
14009 } else if (gameMode == IcsExamining) {
14010 SendToICS(ics_prefix);
14011 SendToICS("tomove black\n");
14016 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14019 ChessSquare piece = boards[0][y][x];
14021 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14023 switch (selection) {
14025 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14026 SendToICS(ics_prefix);
14027 SendToICS("bsetup clear\n");
14028 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14029 SendToICS(ics_prefix);
14030 SendToICS("clearboard\n");
14032 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14033 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14034 for (y = 0; y < BOARD_HEIGHT; y++) {
14035 if (gameMode == IcsExamining) {
14036 if (boards[currentMove][y][x] != EmptySquare) {
14037 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14042 boards[0][y][x] = p;
14047 if (gameMode == EditPosition) {
14048 DrawPosition(FALSE, boards[0]);
14053 SetWhiteToPlayEvent();
14057 SetBlackToPlayEvent();
14061 if (gameMode == IcsExamining) {
14062 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14063 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14066 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14067 if(x == BOARD_LEFT-2) {
14068 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14069 boards[0][y][1] = 0;
14071 if(x == BOARD_RGHT+1) {
14072 if(y >= gameInfo.holdingsSize) break;
14073 boards[0][y][BOARD_WIDTH-2] = 0;
14076 boards[0][y][x] = EmptySquare;
14077 DrawPosition(FALSE, boards[0]);
14082 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14083 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14084 selection = (ChessSquare) (PROMOTED piece);
14085 } else if(piece == EmptySquare) selection = WhiteSilver;
14086 else selection = (ChessSquare)((int)piece - 1);
14090 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14091 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14092 selection = (ChessSquare) (DEMOTED piece);
14093 } else if(piece == EmptySquare) selection = BlackSilver;
14094 else selection = (ChessSquare)((int)piece + 1);
14099 if(gameInfo.variant == VariantShatranj ||
14100 gameInfo.variant == VariantXiangqi ||
14101 gameInfo.variant == VariantCourier ||
14102 gameInfo.variant == VariantMakruk )
14103 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14108 if(gameInfo.variant == VariantXiangqi)
14109 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14110 if(gameInfo.variant == VariantKnightmate)
14111 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14114 if (gameMode == IcsExamining) {
14115 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14116 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14117 PieceToChar(selection), AAA + x, ONE + y);
14120 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14122 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14123 n = PieceToNumber(selection - BlackPawn);
14124 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14125 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14126 boards[0][BOARD_HEIGHT-1-n][1]++;
14128 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14129 n = PieceToNumber(selection);
14130 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14131 boards[0][n][BOARD_WIDTH-1] = selection;
14132 boards[0][n][BOARD_WIDTH-2]++;
14135 boards[0][y][x] = selection;
14136 DrawPosition(TRUE, boards[0]);
14138 fromX = fromY = -1;
14146 DropMenuEvent (ChessSquare selection, int x, int y)
14148 ChessMove moveType;
14150 switch (gameMode) {
14151 case IcsPlayingWhite:
14152 case MachinePlaysBlack:
14153 if (!WhiteOnMove(currentMove)) {
14154 DisplayMoveError(_("It is Black's turn"));
14157 moveType = WhiteDrop;
14159 case IcsPlayingBlack:
14160 case MachinePlaysWhite:
14161 if (WhiteOnMove(currentMove)) {
14162 DisplayMoveError(_("It is White's turn"));
14165 moveType = BlackDrop;
14168 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14174 if (moveType == BlackDrop && selection < BlackPawn) {
14175 selection = (ChessSquare) ((int) selection
14176 + (int) BlackPawn - (int) WhitePawn);
14178 if (boards[currentMove][y][x] != EmptySquare) {
14179 DisplayMoveError(_("That square is occupied"));
14183 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14189 /* Accept a pending offer of any kind from opponent */
14191 if (appData.icsActive) {
14192 SendToICS(ics_prefix);
14193 SendToICS("accept\n");
14194 } else if (cmailMsgLoaded) {
14195 if (currentMove == cmailOldMove &&
14196 commentList[cmailOldMove] != NULL &&
14197 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14198 "Black offers a draw" : "White offers a draw")) {
14200 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14201 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14203 DisplayError(_("There is no pending offer on this move"), 0);
14204 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14207 /* Not used for offers from chess program */
14214 /* Decline a pending offer of any kind from opponent */
14216 if (appData.icsActive) {
14217 SendToICS(ics_prefix);
14218 SendToICS("decline\n");
14219 } else if (cmailMsgLoaded) {
14220 if (currentMove == cmailOldMove &&
14221 commentList[cmailOldMove] != NULL &&
14222 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14223 "Black offers a draw" : "White offers a draw")) {
14225 AppendComment(cmailOldMove, "Draw declined", TRUE);
14226 DisplayComment(cmailOldMove - 1, "Draw declined");
14229 DisplayError(_("There is no pending offer on this move"), 0);
14232 /* Not used for offers from chess program */
14239 /* Issue ICS rematch command */
14240 if (appData.icsActive) {
14241 SendToICS(ics_prefix);
14242 SendToICS("rematch\n");
14249 /* Call your opponent's flag (claim a win on time) */
14250 if (appData.icsActive) {
14251 SendToICS(ics_prefix);
14252 SendToICS("flag\n");
14254 switch (gameMode) {
14257 case MachinePlaysWhite:
14260 GameEnds(GameIsDrawn, "Both players ran out of time",
14263 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14265 DisplayError(_("Your opponent is not out of time"), 0);
14268 case MachinePlaysBlack:
14271 GameEnds(GameIsDrawn, "Both players ran out of time",
14274 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14276 DisplayError(_("Your opponent is not out of time"), 0);
14284 ClockClick (int which)
14285 { // [HGM] code moved to back-end from winboard.c
14286 if(which) { // black clock
14287 if (gameMode == EditPosition || gameMode == IcsExamining) {
14288 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14289 SetBlackToPlayEvent();
14290 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14291 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14292 } else if (shiftKey) {
14293 AdjustClock(which, -1);
14294 } else if (gameMode == IcsPlayingWhite ||
14295 gameMode == MachinePlaysBlack) {
14298 } else { // white clock
14299 if (gameMode == EditPosition || gameMode == IcsExamining) {
14300 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14301 SetWhiteToPlayEvent();
14302 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14303 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14304 } else if (shiftKey) {
14305 AdjustClock(which, -1);
14306 } else if (gameMode == IcsPlayingBlack ||
14307 gameMode == MachinePlaysWhite) {
14316 /* Offer draw or accept pending draw offer from opponent */
14318 if (appData.icsActive) {
14319 /* Note: tournament rules require draw offers to be
14320 made after you make your move but before you punch
14321 your clock. Currently ICS doesn't let you do that;
14322 instead, you immediately punch your clock after making
14323 a move, but you can offer a draw at any time. */
14325 SendToICS(ics_prefix);
14326 SendToICS("draw\n");
14327 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14328 } else if (cmailMsgLoaded) {
14329 if (currentMove == cmailOldMove &&
14330 commentList[cmailOldMove] != NULL &&
14331 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14332 "Black offers a draw" : "White offers a draw")) {
14333 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14334 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14335 } else if (currentMove == cmailOldMove + 1) {
14336 char *offer = WhiteOnMove(cmailOldMove) ?
14337 "White offers a draw" : "Black offers a draw";
14338 AppendComment(currentMove, offer, TRUE);
14339 DisplayComment(currentMove - 1, offer);
14340 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14342 DisplayError(_("You must make your move before offering a draw"), 0);
14343 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14345 } else if (first.offeredDraw) {
14346 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14348 if (first.sendDrawOffers) {
14349 SendToProgram("draw\n", &first);
14350 userOfferedDraw = TRUE;
14358 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14360 if (appData.icsActive) {
14361 SendToICS(ics_prefix);
14362 SendToICS("adjourn\n");
14364 /* Currently GNU Chess doesn't offer or accept Adjourns */
14372 /* Offer Abort or accept pending Abort offer from opponent */
14374 if (appData.icsActive) {
14375 SendToICS(ics_prefix);
14376 SendToICS("abort\n");
14378 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14385 /* Resign. You can do this even if it's not your turn. */
14387 if (appData.icsActive) {
14388 SendToICS(ics_prefix);
14389 SendToICS("resign\n");
14391 switch (gameMode) {
14392 case MachinePlaysWhite:
14393 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14395 case MachinePlaysBlack:
14396 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14399 if (cmailMsgLoaded) {
14401 if (WhiteOnMove(cmailOldMove)) {
14402 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14404 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14406 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14417 StopObservingEvent ()
14419 /* Stop observing current games */
14420 SendToICS(ics_prefix);
14421 SendToICS("unobserve\n");
14425 StopExaminingEvent ()
14427 /* Stop observing current game */
14428 SendToICS(ics_prefix);
14429 SendToICS("unexamine\n");
14433 ForwardInner (int target)
14435 int limit; int oldSeekGraphUp = seekGraphUp;
14437 if (appData.debugMode)
14438 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14439 target, currentMove, forwardMostMove);
14441 if (gameMode == EditPosition)
14444 seekGraphUp = FALSE;
14445 MarkTargetSquares(1);
14447 if (gameMode == PlayFromGameFile && !pausing)
14450 if (gameMode == IcsExamining && pausing)
14451 limit = pauseExamForwardMostMove;
14453 limit = forwardMostMove;
14455 if (target > limit) target = limit;
14457 if (target > 0 && moveList[target - 1][0]) {
14458 int fromX, fromY, toX, toY;
14459 toX = moveList[target - 1][2] - AAA;
14460 toY = moveList[target - 1][3] - ONE;
14461 if (moveList[target - 1][1] == '@') {
14462 if (appData.highlightLastMove) {
14463 SetHighlights(-1, -1, toX, toY);
14466 fromX = moveList[target - 1][0] - AAA;
14467 fromY = moveList[target - 1][1] - ONE;
14468 if (target == currentMove + 1) {
14469 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14471 if (appData.highlightLastMove) {
14472 SetHighlights(fromX, fromY, toX, toY);
14476 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14477 gameMode == Training || gameMode == PlayFromGameFile ||
14478 gameMode == AnalyzeFile) {
14479 while (currentMove < target) {
14480 SendMoveToProgram(currentMove++, &first);
14483 currentMove = target;
14486 if (gameMode == EditGame || gameMode == EndOfGame) {
14487 whiteTimeRemaining = timeRemaining[0][currentMove];
14488 blackTimeRemaining = timeRemaining[1][currentMove];
14490 DisplayBothClocks();
14491 DisplayMove(currentMove - 1);
14492 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14493 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14494 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14495 DisplayComment(currentMove - 1, commentList[currentMove]);
14497 ClearMap(); // [HGM] exclude: invalidate map
14504 if (gameMode == IcsExamining && !pausing) {
14505 SendToICS(ics_prefix);
14506 SendToICS("forward\n");
14508 ForwardInner(currentMove + 1);
14515 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14516 /* to optimze, we temporarily turn off analysis mode while we feed
14517 * the remaining moves to the engine. Otherwise we get analysis output
14520 if (first.analysisSupport) {
14521 SendToProgram("exit\nforce\n", &first);
14522 first.analyzing = FALSE;
14526 if (gameMode == IcsExamining && !pausing) {
14527 SendToICS(ics_prefix);
14528 SendToICS("forward 999999\n");
14530 ForwardInner(forwardMostMove);
14533 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14534 /* we have fed all the moves, so reactivate analysis mode */
14535 SendToProgram("analyze\n", &first);
14536 first.analyzing = TRUE;
14537 /*first.maybeThinking = TRUE;*/
14538 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14543 BackwardInner (int target)
14545 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14547 if (appData.debugMode)
14548 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14549 target, currentMove, forwardMostMove);
14551 if (gameMode == EditPosition) return;
14552 seekGraphUp = FALSE;
14553 MarkTargetSquares(1);
14554 if (currentMove <= backwardMostMove) {
14556 DrawPosition(full_redraw, boards[currentMove]);
14559 if (gameMode == PlayFromGameFile && !pausing)
14562 if (moveList[target][0]) {
14563 int fromX, fromY, toX, toY;
14564 toX = moveList[target][2] - AAA;
14565 toY = moveList[target][3] - ONE;
14566 if (moveList[target][1] == '@') {
14567 if (appData.highlightLastMove) {
14568 SetHighlights(-1, -1, toX, toY);
14571 fromX = moveList[target][0] - AAA;
14572 fromY = moveList[target][1] - ONE;
14573 if (target == currentMove - 1) {
14574 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14576 if (appData.highlightLastMove) {
14577 SetHighlights(fromX, fromY, toX, toY);
14581 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14582 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14583 while (currentMove > target) {
14584 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14585 // null move cannot be undone. Reload program with move history before it.
14587 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14588 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14590 SendBoard(&first, i);
14591 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14594 SendToProgram("undo\n", &first);
14598 currentMove = target;
14601 if (gameMode == EditGame || gameMode == EndOfGame) {
14602 whiteTimeRemaining = timeRemaining[0][currentMove];
14603 blackTimeRemaining = timeRemaining[1][currentMove];
14605 DisplayBothClocks();
14606 DisplayMove(currentMove - 1);
14607 DrawPosition(full_redraw, boards[currentMove]);
14608 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14609 // [HGM] PV info: routine tests if comment empty
14610 DisplayComment(currentMove - 1, commentList[currentMove]);
14611 ClearMap(); // [HGM] exclude: invalidate map
14617 if (gameMode == IcsExamining && !pausing) {
14618 SendToICS(ics_prefix);
14619 SendToICS("backward\n");
14621 BackwardInner(currentMove - 1);
14628 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14629 /* to optimize, we temporarily turn off analysis mode while we undo
14630 * all the moves. Otherwise we get analysis output after each undo.
14632 if (first.analysisSupport) {
14633 SendToProgram("exit\nforce\n", &first);
14634 first.analyzing = FALSE;
14638 if (gameMode == IcsExamining && !pausing) {
14639 SendToICS(ics_prefix);
14640 SendToICS("backward 999999\n");
14642 BackwardInner(backwardMostMove);
14645 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14646 /* we have fed all the moves, so reactivate analysis mode */
14647 SendToProgram("analyze\n", &first);
14648 first.analyzing = TRUE;
14649 /*first.maybeThinking = TRUE;*/
14650 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14657 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14658 if (to >= forwardMostMove) to = forwardMostMove;
14659 if (to <= backwardMostMove) to = backwardMostMove;
14660 if (to < currentMove) {
14668 RevertEvent (Boolean annotate)
14670 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14673 if (gameMode != IcsExamining) {
14674 DisplayError(_("You are not examining a game"), 0);
14678 DisplayError(_("You can't revert while pausing"), 0);
14681 SendToICS(ics_prefix);
14682 SendToICS("revert\n");
14686 RetractMoveEvent ()
14688 switch (gameMode) {
14689 case MachinePlaysWhite:
14690 case MachinePlaysBlack:
14691 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14692 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14695 if (forwardMostMove < 2) return;
14696 currentMove = forwardMostMove = forwardMostMove - 2;
14697 whiteTimeRemaining = timeRemaining[0][currentMove];
14698 blackTimeRemaining = timeRemaining[1][currentMove];
14699 DisplayBothClocks();
14700 DisplayMove(currentMove - 1);
14701 ClearHighlights();/*!! could figure this out*/
14702 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14703 SendToProgram("remove\n", &first);
14704 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14707 case BeginningOfGame:
14711 case IcsPlayingWhite:
14712 case IcsPlayingBlack:
14713 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14714 SendToICS(ics_prefix);
14715 SendToICS("takeback 2\n");
14717 SendToICS(ics_prefix);
14718 SendToICS("takeback 1\n");
14727 ChessProgramState *cps;
14729 switch (gameMode) {
14730 case MachinePlaysWhite:
14731 if (!WhiteOnMove(forwardMostMove)) {
14732 DisplayError(_("It is your turn"), 0);
14737 case MachinePlaysBlack:
14738 if (WhiteOnMove(forwardMostMove)) {
14739 DisplayError(_("It is your turn"), 0);
14744 case TwoMachinesPlay:
14745 if (WhiteOnMove(forwardMostMove) ==
14746 (first.twoMachinesColor[0] == 'w')) {
14752 case BeginningOfGame:
14756 SendToProgram("?\n", cps);
14760 TruncateGameEvent ()
14763 if (gameMode != EditGame) return;
14770 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14771 if (forwardMostMove > currentMove) {
14772 if (gameInfo.resultDetails != NULL) {
14773 free(gameInfo.resultDetails);
14774 gameInfo.resultDetails = NULL;
14775 gameInfo.result = GameUnfinished;
14777 forwardMostMove = currentMove;
14778 HistorySet(parseList, backwardMostMove, forwardMostMove,
14786 if (appData.noChessProgram) return;
14787 switch (gameMode) {
14788 case MachinePlaysWhite:
14789 if (WhiteOnMove(forwardMostMove)) {
14790 DisplayError(_("Wait until your turn"), 0);
14794 case BeginningOfGame:
14795 case MachinePlaysBlack:
14796 if (!WhiteOnMove(forwardMostMove)) {
14797 DisplayError(_("Wait until your turn"), 0);
14802 DisplayError(_("No hint available"), 0);
14805 SendToProgram("hint\n", &first);
14806 hintRequested = TRUE;
14812 if (appData.noChessProgram) return;
14813 switch (gameMode) {
14814 case MachinePlaysWhite:
14815 if (WhiteOnMove(forwardMostMove)) {
14816 DisplayError(_("Wait until your turn"), 0);
14820 case BeginningOfGame:
14821 case MachinePlaysBlack:
14822 if (!WhiteOnMove(forwardMostMove)) {
14823 DisplayError(_("Wait until your turn"), 0);
14828 EditPositionDone(TRUE);
14830 case TwoMachinesPlay:
14835 SendToProgram("bk\n", &first);
14836 bookOutput[0] = NULLCHAR;
14837 bookRequested = TRUE;
14843 char *tags = PGNTags(&gameInfo);
14844 TagsPopUp(tags, CmailMsg());
14848 /* end button procedures */
14851 PrintPosition (FILE *fp, int move)
14855 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14856 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14857 char c = PieceToChar(boards[move][i][j]);
14858 fputc(c == 'x' ? '.' : c, fp);
14859 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14862 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14863 fprintf(fp, "white to play\n");
14865 fprintf(fp, "black to play\n");
14869 PrintOpponents (FILE *fp)
14871 if (gameInfo.white != NULL) {
14872 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14878 /* Find last component of program's own name, using some heuristics */
14880 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14883 int local = (strcmp(host, "localhost") == 0);
14884 while (!local && (p = strchr(prog, ';')) != NULL) {
14886 while (*p == ' ') p++;
14889 if (*prog == '"' || *prog == '\'') {
14890 q = strchr(prog + 1, *prog);
14892 q = strchr(prog, ' ');
14894 if (q == NULL) q = prog + strlen(prog);
14896 while (p >= prog && *p != '/' && *p != '\\') p--;
14898 if(p == prog && *p == '"') p++;
14900 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14901 memcpy(buf, p, q - p);
14902 buf[q - p] = NULLCHAR;
14910 TimeControlTagValue ()
14913 if (!appData.clockMode) {
14914 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14915 } else if (movesPerSession > 0) {
14916 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14917 } else if (timeIncrement == 0) {
14918 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14920 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14922 return StrSave(buf);
14928 /* This routine is used only for certain modes */
14929 VariantClass v = gameInfo.variant;
14930 ChessMove r = GameUnfinished;
14933 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14934 r = gameInfo.result;
14935 p = gameInfo.resultDetails;
14936 gameInfo.resultDetails = NULL;
14938 ClearGameInfo(&gameInfo);
14939 gameInfo.variant = v;
14941 switch (gameMode) {
14942 case MachinePlaysWhite:
14943 gameInfo.event = StrSave( appData.pgnEventHeader );
14944 gameInfo.site = StrSave(HostName());
14945 gameInfo.date = PGNDate();
14946 gameInfo.round = StrSave("-");
14947 gameInfo.white = StrSave(first.tidy);
14948 gameInfo.black = StrSave(UserName());
14949 gameInfo.timeControl = TimeControlTagValue();
14952 case MachinePlaysBlack:
14953 gameInfo.event = StrSave( appData.pgnEventHeader );
14954 gameInfo.site = StrSave(HostName());
14955 gameInfo.date = PGNDate();
14956 gameInfo.round = StrSave("-");
14957 gameInfo.white = StrSave(UserName());
14958 gameInfo.black = StrSave(first.tidy);
14959 gameInfo.timeControl = TimeControlTagValue();
14962 case TwoMachinesPlay:
14963 gameInfo.event = StrSave( appData.pgnEventHeader );
14964 gameInfo.site = StrSave(HostName());
14965 gameInfo.date = PGNDate();
14968 snprintf(buf, MSG_SIZ, "%d", roundNr);
14969 gameInfo.round = StrSave(buf);
14971 gameInfo.round = StrSave("-");
14973 if (first.twoMachinesColor[0] == 'w') {
14974 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14975 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14977 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14978 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14980 gameInfo.timeControl = TimeControlTagValue();
14984 gameInfo.event = StrSave("Edited game");
14985 gameInfo.site = StrSave(HostName());
14986 gameInfo.date = PGNDate();
14987 gameInfo.round = StrSave("-");
14988 gameInfo.white = StrSave("-");
14989 gameInfo.black = StrSave("-");
14990 gameInfo.result = r;
14991 gameInfo.resultDetails = p;
14995 gameInfo.event = StrSave("Edited position");
14996 gameInfo.site = StrSave(HostName());
14997 gameInfo.date = PGNDate();
14998 gameInfo.round = StrSave("-");
14999 gameInfo.white = StrSave("-");
15000 gameInfo.black = StrSave("-");
15003 case IcsPlayingWhite:
15004 case IcsPlayingBlack:
15009 case PlayFromGameFile:
15010 gameInfo.event = StrSave("Game from non-PGN file");
15011 gameInfo.site = StrSave(HostName());
15012 gameInfo.date = PGNDate();
15013 gameInfo.round = StrSave("-");
15014 gameInfo.white = StrSave("?");
15015 gameInfo.black = StrSave("?");
15024 ReplaceComment (int index, char *text)
15030 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15031 pvInfoList[index-1].depth == len &&
15032 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15033 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15034 while (*text == '\n') text++;
15035 len = strlen(text);
15036 while (len > 0 && text[len - 1] == '\n') len--;
15038 if (commentList[index] != NULL)
15039 free(commentList[index]);
15042 commentList[index] = NULL;
15045 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15046 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15047 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15048 commentList[index] = (char *) malloc(len + 2);
15049 strncpy(commentList[index], text, len);
15050 commentList[index][len] = '\n';
15051 commentList[index][len + 1] = NULLCHAR;
15053 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15055 commentList[index] = (char *) malloc(len + 7);
15056 safeStrCpy(commentList[index], "{\n", 3);
15057 safeStrCpy(commentList[index]+2, text, len+1);
15058 commentList[index][len+2] = NULLCHAR;
15059 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15060 strcat(commentList[index], "\n}\n");
15065 CrushCRs (char *text)
15073 if (ch == '\r') continue;
15075 } while (ch != '\0');
15079 AppendComment (int index, char *text, Boolean addBraces)
15080 /* addBraces tells if we should add {} */
15085 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15086 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15089 while (*text == '\n') text++;
15090 len = strlen(text);
15091 while (len > 0 && text[len - 1] == '\n') len--;
15092 text[len] = NULLCHAR;
15094 if (len == 0) return;
15096 if (commentList[index] != NULL) {
15097 Boolean addClosingBrace = addBraces;
15098 old = commentList[index];
15099 oldlen = strlen(old);
15100 while(commentList[index][oldlen-1] == '\n')
15101 commentList[index][--oldlen] = NULLCHAR;
15102 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15103 safeStrCpy(commentList[index], old, oldlen + len + 6);
15105 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15106 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15107 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15108 while (*text == '\n') { text++; len--; }
15109 commentList[index][--oldlen] = NULLCHAR;
15111 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15112 else strcat(commentList[index], "\n");
15113 strcat(commentList[index], text);
15114 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15115 else strcat(commentList[index], "\n");
15117 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15119 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15120 else commentList[index][0] = NULLCHAR;
15121 strcat(commentList[index], text);
15122 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15123 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15128 FindStr (char * text, char * sub_text)
15130 char * result = strstr( text, sub_text );
15132 if( result != NULL ) {
15133 result += strlen( sub_text );
15139 /* [AS] Try to extract PV info from PGN comment */
15140 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15142 GetInfoFromComment (int index, char * text)
15144 char * sep = text, *p;
15146 if( text != NULL && index > 0 ) {
15149 int time = -1, sec = 0, deci;
15150 char * s_eval = FindStr( text, "[%eval " );
15151 char * s_emt = FindStr( text, "[%emt " );
15153 if( s_eval != NULL || s_emt != NULL ) {
15157 if( s_eval != NULL ) {
15158 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15162 if( delim != ']' ) {
15167 if( s_emt != NULL ) {
15172 /* We expect something like: [+|-]nnn.nn/dd */
15175 if(*text != '{') return text; // [HGM] braces: must be normal comment
15177 sep = strchr( text, '/' );
15178 if( sep == NULL || sep < (text+4) ) {
15183 if(p[1] == '(') { // comment starts with PV
15184 p = strchr(p, ')'); // locate end of PV
15185 if(p == NULL || sep < p+5) return text;
15186 // at this point we have something like "{(.*) +0.23/6 ..."
15187 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15188 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15189 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15191 time = -1; sec = -1; deci = -1;
15192 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15193 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15194 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15195 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15199 if( score_lo < 0 || score_lo >= 100 ) {
15203 if(sec >= 0) time = 600*time + 10*sec; else
15204 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15206 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15208 /* [HGM] PV time: now locate end of PV info */
15209 while( *++sep >= '0' && *sep <= '9'); // strip depth
15211 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15213 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15215 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15216 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15227 pvInfoList[index-1].depth = depth;
15228 pvInfoList[index-1].score = score;
15229 pvInfoList[index-1].time = 10*time; // centi-sec
15230 if(*sep == '}') *sep = 0; else *--sep = '{';
15231 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15237 SendToProgram (char *message, ChessProgramState *cps)
15239 int count, outCount, error;
15242 if (cps->pr == NoProc) return;
15245 if (appData.debugMode) {
15248 fprintf(debugFP, "%ld >%-6s: %s",
15249 SubtractTimeMarks(&now, &programStartTime),
15250 cps->which, message);
15252 fprintf(serverFP, "%ld >%-6s: %s",
15253 SubtractTimeMarks(&now, &programStartTime),
15254 cps->which, message), fflush(serverFP);
15257 count = strlen(message);
15258 outCount = OutputToProcess(cps->pr, message, count, &error);
15259 if (outCount < count && !exiting
15260 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15261 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15262 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15263 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15264 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15265 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15266 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15267 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15269 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15270 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15271 gameInfo.result = res;
15273 gameInfo.resultDetails = StrSave(buf);
15275 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15276 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15281 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15285 ChessProgramState *cps = (ChessProgramState *)closure;
15287 if (isr != cps->isr) return; /* Killed intentionally */
15290 RemoveInputSource(cps->isr);
15291 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15292 _(cps->which), cps->program);
15293 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15294 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15295 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15296 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15297 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15298 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15300 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15301 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15302 gameInfo.result = res;
15304 gameInfo.resultDetails = StrSave(buf);
15306 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15307 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15309 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15310 _(cps->which), cps->program);
15311 RemoveInputSource(cps->isr);
15313 /* [AS] Program is misbehaving badly... kill it */
15314 if( count == -2 ) {
15315 DestroyChildProcess( cps->pr, 9 );
15319 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15324 if ((end_str = strchr(message, '\r')) != NULL)
15325 *end_str = NULLCHAR;
15326 if ((end_str = strchr(message, '\n')) != NULL)
15327 *end_str = NULLCHAR;
15329 if (appData.debugMode) {
15330 TimeMark now; int print = 1;
15331 char *quote = ""; char c; int i;
15333 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15334 char start = message[0];
15335 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15336 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15337 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15338 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15339 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15340 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15341 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15342 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15343 sscanf(message, "hint: %c", &c)!=1 &&
15344 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15345 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15346 print = (appData.engineComments >= 2);
15348 message[0] = start; // restore original message
15352 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15353 SubtractTimeMarks(&now, &programStartTime), cps->which,
15357 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15358 SubtractTimeMarks(&now, &programStartTime), cps->which,
15360 message), fflush(serverFP);
15364 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15365 if (appData.icsEngineAnalyze) {
15366 if (strstr(message, "whisper") != NULL ||
15367 strstr(message, "kibitz") != NULL ||
15368 strstr(message, "tellics") != NULL) return;
15371 HandleMachineMove(message, cps);
15376 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15381 if( timeControl_2 > 0 ) {
15382 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15383 tc = timeControl_2;
15386 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15387 inc /= cps->timeOdds;
15388 st /= cps->timeOdds;
15390 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15393 /* Set exact time per move, normally using st command */
15394 if (cps->stKludge) {
15395 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15397 if (seconds == 0) {
15398 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15400 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15403 snprintf(buf, MSG_SIZ, "st %d\n", st);
15406 /* Set conventional or incremental time control, using level command */
15407 if (seconds == 0) {
15408 /* Note old gnuchess bug -- minutes:seconds used to not work.
15409 Fixed in later versions, but still avoid :seconds
15410 when seconds is 0. */
15411 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15413 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15414 seconds, inc/1000.);
15417 SendToProgram(buf, cps);
15419 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15420 /* Orthogonally, limit search to given depth */
15422 if (cps->sdKludge) {
15423 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15425 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15427 SendToProgram(buf, cps);
15430 if(cps->nps >= 0) { /* [HGM] nps */
15431 if(cps->supportsNPS == FALSE)
15432 cps->nps = -1; // don't use if engine explicitly says not supported!
15434 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15435 SendToProgram(buf, cps);
15440 ChessProgramState *
15442 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15444 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15445 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15451 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15453 char message[MSG_SIZ];
15456 /* Note: this routine must be called when the clocks are stopped
15457 or when they have *just* been set or switched; otherwise
15458 it will be off by the time since the current tick started.
15460 if (machineWhite) {
15461 time = whiteTimeRemaining / 10;
15462 otime = blackTimeRemaining / 10;
15464 time = blackTimeRemaining / 10;
15465 otime = whiteTimeRemaining / 10;
15467 /* [HGM] translate opponent's time by time-odds factor */
15468 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15470 if (time <= 0) time = 1;
15471 if (otime <= 0) otime = 1;
15473 snprintf(message, MSG_SIZ, "time %ld\n", time);
15474 SendToProgram(message, cps);
15476 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15477 SendToProgram(message, cps);
15481 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15484 int len = strlen(name);
15487 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15489 sscanf(*p, "%d", &val);
15491 while (**p && **p != ' ')
15493 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15494 SendToProgram(buf, cps);
15501 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15504 int len = strlen(name);
15505 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15507 sscanf(*p, "%d", loc);
15508 while (**p && **p != ' ') (*p)++;
15509 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15510 SendToProgram(buf, cps);
15517 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15520 int len = strlen(name);
15521 if (strncmp((*p), name, len) == 0
15522 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15524 sscanf(*p, "%[^\"]", loc);
15525 while (**p && **p != '\"') (*p)++;
15526 if (**p == '\"') (*p)++;
15527 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15528 SendToProgram(buf, cps);
15535 ParseOption (Option *opt, ChessProgramState *cps)
15536 // [HGM] options: process the string that defines an engine option, and determine
15537 // name, type, default value, and allowed value range
15539 char *p, *q, buf[MSG_SIZ];
15540 int n, min = (-1)<<31, max = 1<<31, def;
15542 if(p = strstr(opt->name, " -spin ")) {
15543 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15544 if(max < min) max = min; // enforce consistency
15545 if(def < min) def = min;
15546 if(def > max) def = max;
15551 } else if((p = strstr(opt->name, " -slider "))) {
15552 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15553 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15554 if(max < min) max = min; // enforce consistency
15555 if(def < min) def = min;
15556 if(def > max) def = max;
15560 opt->type = Spin; // Slider;
15561 } else if((p = strstr(opt->name, " -string "))) {
15562 opt->textValue = p+9;
15563 opt->type = TextBox;
15564 } else if((p = strstr(opt->name, " -file "))) {
15565 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15566 opt->textValue = p+7;
15567 opt->type = FileName; // FileName;
15568 } else if((p = strstr(opt->name, " -path "))) {
15569 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15570 opt->textValue = p+7;
15571 opt->type = PathName; // PathName;
15572 } else if(p = strstr(opt->name, " -check ")) {
15573 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15574 opt->value = (def != 0);
15575 opt->type = CheckBox;
15576 } else if(p = strstr(opt->name, " -combo ")) {
15577 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15578 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15579 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15580 opt->value = n = 0;
15581 while(q = StrStr(q, " /// ")) {
15582 n++; *q = 0; // count choices, and null-terminate each of them
15584 if(*q == '*') { // remember default, which is marked with * prefix
15588 cps->comboList[cps->comboCnt++] = q;
15590 cps->comboList[cps->comboCnt++] = NULL;
15592 opt->type = ComboBox;
15593 } else if(p = strstr(opt->name, " -button")) {
15594 opt->type = Button;
15595 } else if(p = strstr(opt->name, " -save")) {
15596 opt->type = SaveButton;
15597 } else return FALSE;
15598 *p = 0; // terminate option name
15599 // now look if the command-line options define a setting for this engine option.
15600 if(cps->optionSettings && cps->optionSettings[0])
15601 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15602 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15603 snprintf(buf, MSG_SIZ, "option %s", p);
15604 if(p = strstr(buf, ",")) *p = 0;
15605 if(q = strchr(buf, '=')) switch(opt->type) {
15607 for(n=0; n<opt->max; n++)
15608 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15611 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15615 opt->value = atoi(q+1);
15620 SendToProgram(buf, cps);
15626 FeatureDone (ChessProgramState *cps, int val)
15628 DelayedEventCallback cb = GetDelayedEvent();
15629 if ((cb == InitBackEnd3 && cps == &first) ||
15630 (cb == SettingsMenuIfReady && cps == &second) ||
15631 (cb == LoadEngine) ||
15632 (cb == TwoMachinesEventIfReady)) {
15633 CancelDelayedEvent();
15634 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15636 cps->initDone = val;
15639 /* Parse feature command from engine */
15641 ParseFeatures (char *args, ChessProgramState *cps)
15649 while (*p == ' ') p++;
15650 if (*p == NULLCHAR) return;
15652 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15653 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15654 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15655 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15656 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15657 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15658 if (BoolFeature(&p, "reuse", &val, cps)) {
15659 /* Engine can disable reuse, but can't enable it if user said no */
15660 if (!val) cps->reuse = FALSE;
15663 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15664 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15665 if (gameMode == TwoMachinesPlay) {
15666 DisplayTwoMachinesTitle();
15672 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15673 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15674 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15675 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15676 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15677 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15678 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15679 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15680 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15681 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15682 if (IntFeature(&p, "done", &val, cps)) {
15683 FeatureDone(cps, val);
15686 /* Added by Tord: */
15687 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15688 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15689 /* End of additions by Tord */
15691 /* [HGM] added features: */
15692 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15693 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15694 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15695 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15696 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15697 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15698 if (StringFeature(&p, "option", buf, cps)) {
15699 FREE(cps->option[cps->nrOptions].name);
15700 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15701 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15702 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15703 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15704 SendToProgram(buf, cps);
15707 if(cps->nrOptions >= MAX_OPTIONS) {
15709 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15710 DisplayError(buf, 0);
15714 /* End of additions by HGM */
15716 /* unknown feature: complain and skip */
15718 while (*q && *q != '=') q++;
15719 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15720 SendToProgram(buf, cps);
15726 while (*p && *p != '\"') p++;
15727 if (*p == '\"') p++;
15729 while (*p && *p != ' ') p++;
15737 PeriodicUpdatesEvent (int newState)
15739 if (newState == appData.periodicUpdates)
15742 appData.periodicUpdates=newState;
15744 /* Display type changes, so update it now */
15745 // DisplayAnalysis();
15747 /* Get the ball rolling again... */
15749 AnalysisPeriodicEvent(1);
15750 StartAnalysisClock();
15755 PonderNextMoveEvent (int newState)
15757 if (newState == appData.ponderNextMove) return;
15758 if (gameMode == EditPosition) EditPositionDone(TRUE);
15760 SendToProgram("hard\n", &first);
15761 if (gameMode == TwoMachinesPlay) {
15762 SendToProgram("hard\n", &second);
15765 SendToProgram("easy\n", &first);
15766 thinkOutput[0] = NULLCHAR;
15767 if (gameMode == TwoMachinesPlay) {
15768 SendToProgram("easy\n", &second);
15771 appData.ponderNextMove = newState;
15775 NewSettingEvent (int option, int *feature, char *command, int value)
15779 if (gameMode == EditPosition) EditPositionDone(TRUE);
15780 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15781 if(feature == NULL || *feature) SendToProgram(buf, &first);
15782 if (gameMode == TwoMachinesPlay) {
15783 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15788 ShowThinkingEvent ()
15789 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15791 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15792 int newState = appData.showThinking
15793 // [HGM] thinking: other features now need thinking output as well
15794 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15796 if (oldState == newState) return;
15797 oldState = newState;
15798 if (gameMode == EditPosition) EditPositionDone(TRUE);
15800 SendToProgram("post\n", &first);
15801 if (gameMode == TwoMachinesPlay) {
15802 SendToProgram("post\n", &second);
15805 SendToProgram("nopost\n", &first);
15806 thinkOutput[0] = NULLCHAR;
15807 if (gameMode == TwoMachinesPlay) {
15808 SendToProgram("nopost\n", &second);
15811 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15815 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15817 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15818 if (pr == NoProc) return;
15819 AskQuestion(title, question, replyPrefix, pr);
15823 TypeInEvent (char firstChar)
15825 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15826 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15827 gameMode == AnalyzeMode || gameMode == EditGame ||
15828 gameMode == EditPosition || gameMode == IcsExamining ||
15829 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15830 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15831 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15832 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15833 gameMode == Training) PopUpMoveDialog(firstChar);
15837 TypeInDoneEvent (char *move)
15840 int n, fromX, fromY, toX, toY;
15842 ChessMove moveType;
15845 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15846 EditPositionPasteFEN(move);
15849 // [HGM] movenum: allow move number to be typed in any mode
15850 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15854 // undocumented kludge: allow command-line option to be typed in!
15855 // (potentially fatal, and does not implement the effect of the option.)
15856 // should only be used for options that are values on which future decisions will be made,
15857 // and definitely not on options that would be used during initialization.
15858 if(strstr(move, "!!! -") == move) {
15859 ParseArgsFromString(move+4);
15863 if (gameMode != EditGame && currentMove != forwardMostMove &&
15864 gameMode != Training) {
15865 DisplayMoveError(_("Displayed move is not current"));
15867 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15868 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15869 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15870 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15871 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15872 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15874 DisplayMoveError(_("Could not parse move"));
15880 DisplayMove (int moveNumber)
15882 char message[MSG_SIZ];
15884 char cpThinkOutput[MSG_SIZ];
15886 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15888 if (moveNumber == forwardMostMove - 1 ||
15889 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15891 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15893 if (strchr(cpThinkOutput, '\n')) {
15894 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15897 *cpThinkOutput = NULLCHAR;
15900 /* [AS] Hide thinking from human user */
15901 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15902 *cpThinkOutput = NULLCHAR;
15903 if( thinkOutput[0] != NULLCHAR ) {
15906 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15907 cpThinkOutput[i] = '.';
15909 cpThinkOutput[i] = NULLCHAR;
15910 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15914 if (moveNumber == forwardMostMove - 1 &&
15915 gameInfo.resultDetails != NULL) {
15916 if (gameInfo.resultDetails[0] == NULLCHAR) {
15917 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15919 snprintf(res, MSG_SIZ, " {%s} %s",
15920 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15926 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15927 DisplayMessage(res, cpThinkOutput);
15929 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15930 WhiteOnMove(moveNumber) ? " " : ".. ",
15931 parseList[moveNumber], res);
15932 DisplayMessage(message, cpThinkOutput);
15937 DisplayComment (int moveNumber, char *text)
15939 char title[MSG_SIZ];
15941 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15942 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15944 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15945 WhiteOnMove(moveNumber) ? " " : ".. ",
15946 parseList[moveNumber]);
15948 if (text != NULL && (appData.autoDisplayComment || commentUp))
15949 CommentPopUp(title, text);
15952 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15953 * might be busy thinking or pondering. It can be omitted if your
15954 * gnuchess is configured to stop thinking immediately on any user
15955 * input. However, that gnuchess feature depends on the FIONREAD
15956 * ioctl, which does not work properly on some flavors of Unix.
15959 Attention (ChessProgramState *cps)
15962 if (!cps->useSigint) return;
15963 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15964 switch (gameMode) {
15965 case MachinePlaysWhite:
15966 case MachinePlaysBlack:
15967 case TwoMachinesPlay:
15968 case IcsPlayingWhite:
15969 case IcsPlayingBlack:
15972 /* Skip if we know it isn't thinking */
15973 if (!cps->maybeThinking) return;
15974 if (appData.debugMode)
15975 fprintf(debugFP, "Interrupting %s\n", cps->which);
15976 InterruptChildProcess(cps->pr);
15977 cps->maybeThinking = FALSE;
15982 #endif /*ATTENTION*/
15988 if (whiteTimeRemaining <= 0) {
15991 if (appData.icsActive) {
15992 if (appData.autoCallFlag &&
15993 gameMode == IcsPlayingBlack && !blackFlag) {
15994 SendToICS(ics_prefix);
15995 SendToICS("flag\n");
15999 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16001 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16002 if (appData.autoCallFlag) {
16003 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16010 if (blackTimeRemaining <= 0) {
16013 if (appData.icsActive) {
16014 if (appData.autoCallFlag &&
16015 gameMode == IcsPlayingWhite && !whiteFlag) {
16016 SendToICS(ics_prefix);
16017 SendToICS("flag\n");
16021 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16023 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16024 if (appData.autoCallFlag) {
16025 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16036 CheckTimeControl ()
16038 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16039 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16042 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16044 if ( !WhiteOnMove(forwardMostMove) ) {
16045 /* White made time control */
16046 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16047 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16048 /* [HGM] time odds: correct new time quota for time odds! */
16049 / WhitePlayer()->timeOdds;
16050 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16052 lastBlack -= blackTimeRemaining;
16053 /* Black made time control */
16054 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16055 / WhitePlayer()->other->timeOdds;
16056 lastWhite = whiteTimeRemaining;
16061 DisplayBothClocks ()
16063 int wom = gameMode == EditPosition ?
16064 !blackPlaysFirst : WhiteOnMove(currentMove);
16065 DisplayWhiteClock(whiteTimeRemaining, wom);
16066 DisplayBlackClock(blackTimeRemaining, !wom);
16070 /* Timekeeping seems to be a portability nightmare. I think everyone
16071 has ftime(), but I'm really not sure, so I'm including some ifdefs
16072 to use other calls if you don't. Clocks will be less accurate if
16073 you have neither ftime nor gettimeofday.
16076 /* VS 2008 requires the #include outside of the function */
16077 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16078 #include <sys/timeb.h>
16081 /* Get the current time as a TimeMark */
16083 GetTimeMark (TimeMark *tm)
16085 #if HAVE_GETTIMEOFDAY
16087 struct timeval timeVal;
16088 struct timezone timeZone;
16090 gettimeofday(&timeVal, &timeZone);
16091 tm->sec = (long) timeVal.tv_sec;
16092 tm->ms = (int) (timeVal.tv_usec / 1000L);
16094 #else /*!HAVE_GETTIMEOFDAY*/
16097 // include <sys/timeb.h> / moved to just above start of function
16098 struct timeb timeB;
16101 tm->sec = (long) timeB.time;
16102 tm->ms = (int) timeB.millitm;
16104 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16105 tm->sec = (long) time(NULL);
16111 /* Return the difference in milliseconds between two
16112 time marks. We assume the difference will fit in a long!
16115 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16117 return 1000L*(tm2->sec - tm1->sec) +
16118 (long) (tm2->ms - tm1->ms);
16123 * Code to manage the game clocks.
16125 * In tournament play, black starts the clock and then white makes a move.
16126 * We give the human user a slight advantage if he is playing white---the
16127 * clocks don't run until he makes his first move, so it takes zero time.
16128 * Also, we don't account for network lag, so we could get out of sync
16129 * with GNU Chess's clock -- but then, referees are always right.
16132 static TimeMark tickStartTM;
16133 static long intendedTickLength;
16136 NextTickLength (long timeRemaining)
16138 long nominalTickLength, nextTickLength;
16140 if (timeRemaining > 0L && timeRemaining <= 10000L)
16141 nominalTickLength = 100L;
16143 nominalTickLength = 1000L;
16144 nextTickLength = timeRemaining % nominalTickLength;
16145 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16147 return nextTickLength;
16150 /* Adjust clock one minute up or down */
16152 AdjustClock (Boolean which, int dir)
16154 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16155 if(which) blackTimeRemaining += 60000*dir;
16156 else whiteTimeRemaining += 60000*dir;
16157 DisplayBothClocks();
16158 adjustedClock = TRUE;
16161 /* Stop clocks and reset to a fresh time control */
16165 (void) StopClockTimer();
16166 if (appData.icsActive) {
16167 whiteTimeRemaining = blackTimeRemaining = 0;
16168 } else if (searchTime) {
16169 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16170 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16171 } else { /* [HGM] correct new time quote for time odds */
16172 whiteTC = blackTC = fullTimeControlString;
16173 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16174 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16176 if (whiteFlag || blackFlag) {
16178 whiteFlag = blackFlag = FALSE;
16180 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16181 DisplayBothClocks();
16182 adjustedClock = FALSE;
16185 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16187 /* Decrement running clock by amount of time that has passed */
16191 long timeRemaining;
16192 long lastTickLength, fudge;
16195 if (!appData.clockMode) return;
16196 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16200 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16202 /* Fudge if we woke up a little too soon */
16203 fudge = intendedTickLength - lastTickLength;
16204 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16206 if (WhiteOnMove(forwardMostMove)) {
16207 if(whiteNPS >= 0) lastTickLength = 0;
16208 timeRemaining = whiteTimeRemaining -= lastTickLength;
16209 if(timeRemaining < 0 && !appData.icsActive) {
16210 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16211 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16212 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16213 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16216 DisplayWhiteClock(whiteTimeRemaining - fudge,
16217 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16219 if(blackNPS >= 0) lastTickLength = 0;
16220 timeRemaining = blackTimeRemaining -= lastTickLength;
16221 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16222 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16224 blackStartMove = forwardMostMove;
16225 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16228 DisplayBlackClock(blackTimeRemaining - fudge,
16229 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16231 if (CheckFlags()) return;
16233 if(twoBoards) { // count down secondary board's clocks as well
16234 activePartnerTime -= lastTickLength;
16236 if(activePartner == 'W')
16237 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16239 DisplayBlackClock(activePartnerTime, TRUE);
16244 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16245 StartClockTimer(intendedTickLength);
16247 /* if the time remaining has fallen below the alarm threshold, sound the
16248 * alarm. if the alarm has sounded and (due to a takeback or time control
16249 * with increment) the time remaining has increased to a level above the
16250 * threshold, reset the alarm so it can sound again.
16253 if (appData.icsActive && appData.icsAlarm) {
16255 /* make sure we are dealing with the user's clock */
16256 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16257 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16260 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16261 alarmSounded = FALSE;
16262 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16264 alarmSounded = TRUE;
16270 /* A player has just moved, so stop the previously running
16271 clock and (if in clock mode) start the other one.
16272 We redisplay both clocks in case we're in ICS mode, because
16273 ICS gives us an update to both clocks after every move.
16274 Note that this routine is called *after* forwardMostMove
16275 is updated, so the last fractional tick must be subtracted
16276 from the color that is *not* on move now.
16279 SwitchClocks (int newMoveNr)
16281 long lastTickLength;
16283 int flagged = FALSE;
16287 if (StopClockTimer() && appData.clockMode) {
16288 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16289 if (!WhiteOnMove(forwardMostMove)) {
16290 if(blackNPS >= 0) lastTickLength = 0;
16291 blackTimeRemaining -= lastTickLength;
16292 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16293 // if(pvInfoList[forwardMostMove].time == -1)
16294 pvInfoList[forwardMostMove].time = // use GUI time
16295 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16297 if(whiteNPS >= 0) lastTickLength = 0;
16298 whiteTimeRemaining -= lastTickLength;
16299 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16300 // if(pvInfoList[forwardMostMove].time == -1)
16301 pvInfoList[forwardMostMove].time =
16302 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16304 flagged = CheckFlags();
16306 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16307 CheckTimeControl();
16309 if (flagged || !appData.clockMode) return;
16311 switch (gameMode) {
16312 case MachinePlaysBlack:
16313 case MachinePlaysWhite:
16314 case BeginningOfGame:
16315 if (pausing) return;
16319 case PlayFromGameFile:
16327 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16328 if(WhiteOnMove(forwardMostMove))
16329 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16330 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16334 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16335 whiteTimeRemaining : blackTimeRemaining);
16336 StartClockTimer(intendedTickLength);
16340 /* Stop both clocks */
16344 long lastTickLength;
16347 if (!StopClockTimer()) return;
16348 if (!appData.clockMode) return;
16352 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16353 if (WhiteOnMove(forwardMostMove)) {
16354 if(whiteNPS >= 0) lastTickLength = 0;
16355 whiteTimeRemaining -= lastTickLength;
16356 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16358 if(blackNPS >= 0) lastTickLength = 0;
16359 blackTimeRemaining -= lastTickLength;
16360 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16365 /* Start clock of player on move. Time may have been reset, so
16366 if clock is already running, stop and restart it. */
16370 (void) StopClockTimer(); /* in case it was running already */
16371 DisplayBothClocks();
16372 if (CheckFlags()) return;
16374 if (!appData.clockMode) return;
16375 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16377 GetTimeMark(&tickStartTM);
16378 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16379 whiteTimeRemaining : blackTimeRemaining);
16381 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16382 whiteNPS = blackNPS = -1;
16383 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16384 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16385 whiteNPS = first.nps;
16386 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16387 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16388 blackNPS = first.nps;
16389 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16390 whiteNPS = second.nps;
16391 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16392 blackNPS = second.nps;
16393 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16395 StartClockTimer(intendedTickLength);
16399 TimeString (long ms)
16401 long second, minute, hour, day;
16403 static char buf[32];
16405 if (ms > 0 && ms <= 9900) {
16406 /* convert milliseconds to tenths, rounding up */
16407 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16409 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16413 /* convert milliseconds to seconds, rounding up */
16414 /* use floating point to avoid strangeness of integer division
16415 with negative dividends on many machines */
16416 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16423 day = second / (60 * 60 * 24);
16424 second = second % (60 * 60 * 24);
16425 hour = second / (60 * 60);
16426 second = second % (60 * 60);
16427 minute = second / 60;
16428 second = second % 60;
16431 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16432 sign, day, hour, minute, second);
16434 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16436 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16443 * This is necessary because some C libraries aren't ANSI C compliant yet.
16446 StrStr (char *string, char *match)
16450 length = strlen(match);
16452 for (i = strlen(string) - length; i >= 0; i--, string++)
16453 if (!strncmp(match, string, length))
16460 StrCaseStr (char *string, char *match)
16464 length = strlen(match);
16466 for (i = strlen(string) - length; i >= 0; i--, string++) {
16467 for (j = 0; j < length; j++) {
16468 if (ToLower(match[j]) != ToLower(string[j]))
16471 if (j == length) return string;
16479 StrCaseCmp (char *s1, char *s2)
16484 c1 = ToLower(*s1++);
16485 c2 = ToLower(*s2++);
16486 if (c1 > c2) return 1;
16487 if (c1 < c2) return -1;
16488 if (c1 == NULLCHAR) return 0;
16496 return isupper(c) ? tolower(c) : c;
16503 return islower(c) ? toupper(c) : c;
16505 #endif /* !_amigados */
16512 if ((ret = (char *) malloc(strlen(s) + 1)))
16514 safeStrCpy(ret, s, strlen(s)+1);
16520 StrSavePtr (char *s, char **savePtr)
16525 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16526 safeStrCpy(*savePtr, s, strlen(s)+1);
16538 clock = time((time_t *)NULL);
16539 tm = localtime(&clock);
16540 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16541 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16542 return StrSave(buf);
16547 PositionToFEN (int move, char *overrideCastling)
16549 int i, j, fromX, fromY, toX, toY;
16556 whiteToPlay = (gameMode == EditPosition) ?
16557 !blackPlaysFirst : (move % 2 == 0);
16560 /* Piece placement data */
16561 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16562 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16564 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16565 if (boards[move][i][j] == EmptySquare) {
16567 } else { ChessSquare piece = boards[move][i][j];
16568 if (emptycount > 0) {
16569 if(emptycount<10) /* [HGM] can be >= 10 */
16570 *p++ = '0' + emptycount;
16571 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16574 if(PieceToChar(piece) == '+') {
16575 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16577 piece = (ChessSquare)(DEMOTED piece);
16579 *p++ = PieceToChar(piece);
16581 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16582 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16587 if (emptycount > 0) {
16588 if(emptycount<10) /* [HGM] can be >= 10 */
16589 *p++ = '0' + emptycount;
16590 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16597 /* [HGM] print Crazyhouse or Shogi holdings */
16598 if( gameInfo.holdingsWidth ) {
16599 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16601 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16602 piece = boards[move][i][BOARD_WIDTH-1];
16603 if( piece != EmptySquare )
16604 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16605 *p++ = PieceToChar(piece);
16607 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16608 piece = boards[move][BOARD_HEIGHT-i-1][0];
16609 if( piece != EmptySquare )
16610 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16611 *p++ = PieceToChar(piece);
16614 if( q == p ) *p++ = '-';
16620 *p++ = whiteToPlay ? 'w' : 'b';
16623 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16624 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16626 if(nrCastlingRights) {
16628 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16629 /* [HGM] write directly from rights */
16630 if(boards[move][CASTLING][2] != NoRights &&
16631 boards[move][CASTLING][0] != NoRights )
16632 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16633 if(boards[move][CASTLING][2] != NoRights &&
16634 boards[move][CASTLING][1] != NoRights )
16635 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16636 if(boards[move][CASTLING][5] != NoRights &&
16637 boards[move][CASTLING][3] != NoRights )
16638 *p++ = boards[move][CASTLING][3] + AAA;
16639 if(boards[move][CASTLING][5] != NoRights &&
16640 boards[move][CASTLING][4] != NoRights )
16641 *p++ = boards[move][CASTLING][4] + AAA;
16644 /* [HGM] write true castling rights */
16645 if( nrCastlingRights == 6 ) {
16646 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16647 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16648 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16649 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16650 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16651 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16652 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16653 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16656 if (q == p) *p++ = '-'; /* No castling rights */
16660 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16661 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16662 /* En passant target square */
16663 if (move > backwardMostMove) {
16664 fromX = moveList[move - 1][0] - AAA;
16665 fromY = moveList[move - 1][1] - ONE;
16666 toX = moveList[move - 1][2] - AAA;
16667 toY = moveList[move - 1][3] - ONE;
16668 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16669 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16670 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16672 /* 2-square pawn move just happened */
16674 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16678 } else if(move == backwardMostMove) {
16679 // [HGM] perhaps we should always do it like this, and forget the above?
16680 if((signed char)boards[move][EP_STATUS] >= 0) {
16681 *p++ = boards[move][EP_STATUS] + AAA;
16682 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16693 /* [HGM] find reversible plies */
16694 { int i = 0, j=move;
16696 if (appData.debugMode) { int k;
16697 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16698 for(k=backwardMostMove; k<=forwardMostMove; k++)
16699 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16703 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16704 if( j == backwardMostMove ) i += initialRulePlies;
16705 sprintf(p, "%d ", i);
16706 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16708 /* Fullmove number */
16709 sprintf(p, "%d", (move / 2) + 1);
16711 return StrSave(buf);
16715 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16724 /* [HGM] by default clear Crazyhouse holdings, if present */
16725 if(gameInfo.holdingsWidth) {
16726 for(i=0; i<BOARD_HEIGHT; i++) {
16727 board[i][0] = EmptySquare; /* black holdings */
16728 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16729 board[i][1] = (ChessSquare) 0; /* black counts */
16730 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16734 /* Piece placement data */
16735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16738 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16739 if (*p == '/') p++;
16740 emptycount = gameInfo.boardWidth - j;
16741 while (emptycount--)
16742 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16744 #if(BOARD_FILES >= 10)
16745 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16746 p++; emptycount=10;
16747 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16748 while (emptycount--)
16749 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16751 } else if (isdigit(*p)) {
16752 emptycount = *p++ - '0';
16753 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16754 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16755 while (emptycount--)
16756 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16757 } else if (*p == '+' || isalpha(*p)) {
16758 if (j >= gameInfo.boardWidth) return FALSE;
16760 piece = CharToPiece(*++p);
16761 if(piece == EmptySquare) return FALSE; /* unknown piece */
16762 piece = (ChessSquare) (PROMOTED piece ); p++;
16763 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16764 } else piece = CharToPiece(*p++);
16766 if(piece==EmptySquare) return FALSE; /* unknown piece */
16767 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16768 piece = (ChessSquare) (PROMOTED piece);
16769 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16772 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16778 while (*p == '/' || *p == ' ') p++;
16780 /* [HGM] look for Crazyhouse holdings here */
16781 while(*p==' ') p++;
16782 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16784 if(*p == '-' ) p++; /* empty holdings */ else {
16785 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16786 /* if we would allow FEN reading to set board size, we would */
16787 /* have to add holdings and shift the board read so far here */
16788 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16790 if((int) piece >= (int) BlackPawn ) {
16791 i = (int)piece - (int)BlackPawn;
16792 i = PieceToNumber((ChessSquare)i);
16793 if( i >= gameInfo.holdingsSize ) return FALSE;
16794 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16795 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16797 i = (int)piece - (int)WhitePawn;
16798 i = PieceToNumber((ChessSquare)i);
16799 if( i >= gameInfo.holdingsSize ) return FALSE;
16800 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16801 board[i][BOARD_WIDTH-2]++; /* black holdings */
16808 while(*p == ' ') p++;
16812 if(appData.colorNickNames) {
16813 if( c == appData.colorNickNames[0] ) c = 'w'; else
16814 if( c == appData.colorNickNames[1] ) c = 'b';
16818 *blackPlaysFirst = FALSE;
16821 *blackPlaysFirst = TRUE;
16827 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16828 /* return the extra info in global variiables */
16830 /* set defaults in case FEN is incomplete */
16831 board[EP_STATUS] = EP_UNKNOWN;
16832 for(i=0; i<nrCastlingRights; i++ ) {
16833 board[CASTLING][i] =
16834 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16835 } /* assume possible unless obviously impossible */
16836 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16837 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16838 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16839 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16840 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16841 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16842 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16843 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16846 while(*p==' ') p++;
16847 if(nrCastlingRights) {
16848 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16849 /* castling indicator present, so default becomes no castlings */
16850 for(i=0; i<nrCastlingRights; i++ ) {
16851 board[CASTLING][i] = NoRights;
16854 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16855 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16856 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16857 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16858 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16860 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16861 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16862 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16864 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16865 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16866 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16867 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16868 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16869 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16872 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16873 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16874 board[CASTLING][2] = whiteKingFile;
16877 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16878 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16879 board[CASTLING][2] = whiteKingFile;
16882 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16883 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16884 board[CASTLING][5] = blackKingFile;
16887 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16888 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16889 board[CASTLING][5] = blackKingFile;
16892 default: /* FRC castlings */
16893 if(c >= 'a') { /* black rights */
16894 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16895 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16896 if(i == BOARD_RGHT) break;
16897 board[CASTLING][5] = i;
16899 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16900 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16902 board[CASTLING][3] = c;
16904 board[CASTLING][4] = c;
16905 } else { /* white rights */
16906 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16907 if(board[0][i] == WhiteKing) break;
16908 if(i == BOARD_RGHT) break;
16909 board[CASTLING][2] = i;
16910 c -= AAA - 'a' + 'A';
16911 if(board[0][c] >= WhiteKing) break;
16913 board[CASTLING][0] = c;
16915 board[CASTLING][1] = c;
16919 for(i=0; i<nrCastlingRights; i++)
16920 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16921 if (appData.debugMode) {
16922 fprintf(debugFP, "FEN castling rights:");
16923 for(i=0; i<nrCastlingRights; i++)
16924 fprintf(debugFP, " %d", board[CASTLING][i]);
16925 fprintf(debugFP, "\n");
16928 while(*p==' ') p++;
16931 /* read e.p. field in games that know e.p. capture */
16932 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16933 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16935 p++; board[EP_STATUS] = EP_NONE;
16937 char c = *p++ - AAA;
16939 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16940 if(*p >= '0' && *p <='9') p++;
16941 board[EP_STATUS] = c;
16946 if(sscanf(p, "%d", &i) == 1) {
16947 FENrulePlies = i; /* 50-move ply counter */
16948 /* (The move number is still ignored) */
16955 EditPositionPasteFEN (char *fen)
16958 Board initial_position;
16960 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16961 DisplayError(_("Bad FEN position in clipboard"), 0);
16964 int savedBlackPlaysFirst = blackPlaysFirst;
16965 EditPositionEvent();
16966 blackPlaysFirst = savedBlackPlaysFirst;
16967 CopyBoard(boards[0], initial_position);
16968 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16969 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16970 DisplayBothClocks();
16971 DrawPosition(FALSE, boards[currentMove]);
16976 static char cseq[12] = "\\ ";
16979 set_cont_sequence (char *new_seq)
16984 // handle bad attempts to set the sequence
16986 return 0; // acceptable error - no debug
16988 len = strlen(new_seq);
16989 ret = (len > 0) && (len < sizeof(cseq));
16991 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16992 else if (appData.debugMode)
16993 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16998 reformat a source message so words don't cross the width boundary. internal
16999 newlines are not removed. returns the wrapped size (no null character unless
17000 included in source message). If dest is NULL, only calculate the size required
17001 for the dest buffer. lp argument indicats line position upon entry, and it's
17002 passed back upon exit.
17005 wrap (char *dest, char *src, int count, int width, int *lp)
17007 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17009 cseq_len = strlen(cseq);
17010 old_line = line = *lp;
17011 ansi = len = clen = 0;
17013 for (i=0; i < count; i++)
17015 if (src[i] == '\033')
17018 // if we hit the width, back up
17019 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17021 // store i & len in case the word is too long
17022 old_i = i, old_len = len;
17024 // find the end of the last word
17025 while (i && src[i] != ' ' && src[i] != '\n')
17031 // word too long? restore i & len before splitting it
17032 if ((old_i-i+clen) >= width)
17039 if (i && src[i-1] == ' ')
17042 if (src[i] != ' ' && src[i] != '\n')
17049 // now append the newline and continuation sequence
17054 strncpy(dest+len, cseq, cseq_len);
17062 dest[len] = src[i];
17066 if (src[i] == '\n')
17071 if (dest && appData.debugMode)
17073 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17074 count, width, line, len, *lp);
17075 show_bytes(debugFP, src, count);
17076 fprintf(debugFP, "\ndest: ");
17077 show_bytes(debugFP, dest, len);
17078 fprintf(debugFP, "\n");
17080 *lp = dest ? line : old_line;
17085 // [HGM] vari: routines for shelving variations
17086 Boolean modeRestore = FALSE;
17089 PushInner (int firstMove, int lastMove)
17091 int i, j, nrMoves = lastMove - firstMove;
17093 // push current tail of game on stack
17094 savedResult[storedGames] = gameInfo.result;
17095 savedDetails[storedGames] = gameInfo.resultDetails;
17096 gameInfo.resultDetails = NULL;
17097 savedFirst[storedGames] = firstMove;
17098 savedLast [storedGames] = lastMove;
17099 savedFramePtr[storedGames] = framePtr;
17100 framePtr -= nrMoves; // reserve space for the boards
17101 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17102 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17103 for(j=0; j<MOVE_LEN; j++)
17104 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17105 for(j=0; j<2*MOVE_LEN; j++)
17106 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17107 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17108 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17109 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17110 pvInfoList[firstMove+i-1].depth = 0;
17111 commentList[framePtr+i] = commentList[firstMove+i];
17112 commentList[firstMove+i] = NULL;
17116 forwardMostMove = firstMove; // truncate game so we can start variation
17120 PushTail (int firstMove, int lastMove)
17122 if(appData.icsActive) { // only in local mode
17123 forwardMostMove = currentMove; // mimic old ICS behavior
17126 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17128 PushInner(firstMove, lastMove);
17129 if(storedGames == 1) GreyRevert(FALSE);
17130 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17134 PopInner (Boolean annotate)
17137 char buf[8000], moveBuf[20];
17139 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17140 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17141 nrMoves = savedLast[storedGames] - currentMove;
17144 if(!WhiteOnMove(currentMove))
17145 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17146 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17147 for(i=currentMove; i<forwardMostMove; i++) {
17149 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17150 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17151 strcat(buf, moveBuf);
17152 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17153 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17157 for(i=1; i<=nrMoves; i++) { // copy last variation back
17158 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17159 for(j=0; j<MOVE_LEN; j++)
17160 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17161 for(j=0; j<2*MOVE_LEN; j++)
17162 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17163 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17164 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17165 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17166 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17167 commentList[currentMove+i] = commentList[framePtr+i];
17168 commentList[framePtr+i] = NULL;
17170 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17171 framePtr = savedFramePtr[storedGames];
17172 gameInfo.result = savedResult[storedGames];
17173 if(gameInfo.resultDetails != NULL) {
17174 free(gameInfo.resultDetails);
17176 gameInfo.resultDetails = savedDetails[storedGames];
17177 forwardMostMove = currentMove + nrMoves;
17181 PopTail (Boolean annotate)
17183 if(appData.icsActive) return FALSE; // only in local mode
17184 if(!storedGames) return FALSE; // sanity
17185 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17187 PopInner(annotate);
17188 if(currentMove < forwardMostMove) ForwardEvent(); else
17189 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17191 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17197 { // remove all shelved variations
17199 for(i=0; i<storedGames; i++) {
17200 if(savedDetails[i])
17201 free(savedDetails[i]);
17202 savedDetails[i] = NULL;
17204 for(i=framePtr; i<MAX_MOVES; i++) {
17205 if(commentList[i]) free(commentList[i]);
17206 commentList[i] = NULL;
17208 framePtr = MAX_MOVES-1;
17213 LoadVariation (int index, char *text)
17214 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17215 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17216 int level = 0, move;
17218 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17219 // first find outermost bracketing variation
17220 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17221 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17222 if(*p == '{') wait = '}'; else
17223 if(*p == '[') wait = ']'; else
17224 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17225 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17227 if(*p == wait) wait = NULLCHAR; // closing ]} found
17230 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17231 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17232 end[1] = NULLCHAR; // clip off comment beyond variation
17233 ToNrEvent(currentMove-1);
17234 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17235 // kludge: use ParsePV() to append variation to game
17236 move = currentMove;
17237 ParsePV(start, TRUE, TRUE);
17238 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17239 ClearPremoveHighlights();
17241 ToNrEvent(currentMove+1);