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; // [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;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [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 < forwardMostMove; 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);
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 */
4248 for (k = 0; k < ranks; k++) {
4249 for (j = 0; j < files; j++)
4250 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251 if(gameInfo.holdingsWidth > 1) {
4252 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4256 CopyBoard(partnerBoard, board);
4257 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261 if(toSqr = strchr(str, '-')) {
4262 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4268 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4269 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4270 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4271 DisplayMessage(partnerStatus, "");
4272 partnerBoardValid = TRUE;
4276 /* Modify behavior for initial board display on move listing
4279 switch (ics_getting_history) {
4283 case H_GOT_REQ_HEADER:
4284 case H_GOT_UNREQ_HEADER:
4285 /* This is the initial position of the current game */
4286 gamenum = ics_gamenum;
4287 moveNum = 0; /* old ICS bug workaround */
4288 if (to_play == 'B') {
4289 startedFromSetupPosition = TRUE;
4290 blackPlaysFirst = TRUE;
4292 if (forwardMostMove == 0) forwardMostMove = 1;
4293 if (backwardMostMove == 0) backwardMostMove = 1;
4294 if (currentMove == 0) currentMove = 1;
4296 newGameMode = gameMode;
4297 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4299 case H_GOT_UNWANTED_HEADER:
4300 /* This is an initial board that we don't want */
4302 case H_GETTING_MOVES:
4303 /* Should not happen */
4304 DisplayError(_("Error gathering move list: extra board"), 0);
4305 ics_getting_history = H_FALSE;
4309 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4310 weird && (int)gameInfo.variant < (int)VariantShogi) {
4311 /* [HGM] We seem to have switched variant unexpectedly
4312 * Try to guess new variant from board size
4314 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4315 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4316 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4317 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4318 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4319 if(!weird) newVariant = VariantNormal;
4320 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4321 /* Get a move list just to see the header, which
4322 will tell us whether this is really bug or zh */
4323 if (ics_getting_history == H_FALSE) {
4324 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4325 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4330 /* Take action if this is the first board of a new game, or of a
4331 different game than is currently being displayed. */
4332 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4333 relation == RELATION_ISOLATED_BOARD) {
4335 /* Forget the old game and get the history (if any) of the new one */
4336 if (gameMode != BeginningOfGame) {
4340 if (appData.autoRaiseBoard) BoardToTop();
4342 if (gamenum == -1) {
4343 newGameMode = IcsIdle;
4344 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4345 appData.getMoveList && !reqFlag) {
4346 /* Need to get game history */
4347 ics_getting_history = H_REQUESTED;
4348 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352 /* Initially flip the board to have black on the bottom if playing
4353 black or if the ICS flip flag is set, but let the user change
4354 it with the Flip View button. */
4355 flipView = appData.autoFlipView ?
4356 (newGameMode == IcsPlayingBlack) || ics_flip :
4359 /* Done with values from previous mode; copy in new ones */
4360 gameMode = newGameMode;
4362 ics_gamenum = gamenum;
4363 if (gamenum == gs_gamenum) {
4364 int klen = strlen(gs_kind);
4365 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4366 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4367 gameInfo.event = StrSave(str);
4369 gameInfo.event = StrSave("ICS game");
4371 gameInfo.site = StrSave(appData.icsHost);
4372 gameInfo.date = PGNDate();
4373 gameInfo.round = StrSave("-");
4374 gameInfo.white = StrSave(white);
4375 gameInfo.black = StrSave(black);
4376 timeControl = basetime * 60 * 1000;
4378 timeIncrement = increment * 1000;
4379 movesPerSession = 0;
4380 gameInfo.timeControl = TimeControlTagValue();
4381 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4382 if (appData.debugMode) {
4383 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4384 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4385 setbuf(debugFP, NULL);
4388 gameInfo.outOfBook = NULL;
4390 /* Do we have the ratings? */
4391 if (strcmp(player1Name, white) == 0 &&
4392 strcmp(player2Name, black) == 0) {
4393 if (appData.debugMode)
4394 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4395 player1Rating, player2Rating);
4396 gameInfo.whiteRating = player1Rating;
4397 gameInfo.blackRating = player2Rating;
4398 } else if (strcmp(player2Name, white) == 0 &&
4399 strcmp(player1Name, black) == 0) {
4400 if (appData.debugMode)
4401 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4402 player2Rating, player1Rating);
4403 gameInfo.whiteRating = player2Rating;
4404 gameInfo.blackRating = player1Rating;
4406 player1Name[0] = player2Name[0] = NULLCHAR;
4408 /* Silence shouts if requested */
4409 if (appData.quietPlay &&
4410 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4411 SendToICS(ics_prefix);
4412 SendToICS("set shout 0\n");
4416 /* Deal with midgame name changes */
4418 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4419 if (gameInfo.white) free(gameInfo.white);
4420 gameInfo.white = StrSave(white);
4422 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4423 if (gameInfo.black) free(gameInfo.black);
4424 gameInfo.black = StrSave(black);
4428 /* Throw away game result if anything actually changes in examine mode */
4429 if (gameMode == IcsExamining && !newGame) {
4430 gameInfo.result = GameUnfinished;
4431 if (gameInfo.resultDetails != NULL) {
4432 free(gameInfo.resultDetails);
4433 gameInfo.resultDetails = NULL;
4437 /* In pausing && IcsExamining mode, we ignore boards coming
4438 in if they are in a different variation than we are. */
4439 if (pauseExamInvalid) return;
4440 if (pausing && gameMode == IcsExamining) {
4441 if (moveNum <= pauseExamForwardMostMove) {
4442 pauseExamInvalid = TRUE;
4443 forwardMostMove = pauseExamForwardMostMove;
4448 if (appData.debugMode) {
4449 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4451 /* Parse the board */
4452 for (k = 0; k < ranks; k++) {
4453 for (j = 0; j < files; j++)
4454 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4455 if(gameInfo.holdingsWidth > 1) {
4456 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4457 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4460 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4461 board[5][BOARD_RGHT+1] = WhiteAngel;
4462 board[6][BOARD_RGHT+1] = WhiteMarshall;
4463 board[1][0] = BlackMarshall;
4464 board[2][0] = BlackAngel;
4465 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4467 CopyBoard(boards[moveNum], board);
4468 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4470 startedFromSetupPosition =
4471 !CompareBoards(board, initialPosition);
4472 if(startedFromSetupPosition)
4473 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4476 /* [HGM] Set castling rights. Take the outermost Rooks,
4477 to make it also work for FRC opening positions. Note that board12
4478 is really defective for later FRC positions, as it has no way to
4479 indicate which Rook can castle if they are on the same side of King.
4480 For the initial position we grant rights to the outermost Rooks,
4481 and remember thos rights, and we then copy them on positions
4482 later in an FRC game. This means WB might not recognize castlings with
4483 Rooks that have moved back to their original position as illegal,
4484 but in ICS mode that is not its job anyway.
4486 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4487 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4489 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4490 if(board[0][i] == WhiteRook) j = i;
4491 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4492 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4493 if(board[0][i] == WhiteRook) j = i;
4494 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4497 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4502 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4503 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4504 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4505 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4506 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4507 if(board[BOARD_HEIGHT-1][k] == bKing)
4508 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4509 if(gameInfo.variant == VariantTwoKings) {
4510 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4511 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4512 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4515 r = boards[moveNum][CASTLING][0] = initialRights[0];
4516 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4517 r = boards[moveNum][CASTLING][1] = initialRights[1];
4518 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4519 r = boards[moveNum][CASTLING][3] = initialRights[3];
4520 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4521 r = boards[moveNum][CASTLING][4] = initialRights[4];
4522 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4523 /* wildcastle kludge: always assume King has rights */
4524 r = boards[moveNum][CASTLING][2] = initialRights[2];
4525 r = boards[moveNum][CASTLING][5] = initialRights[5];
4527 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4528 boards[moveNum][EP_STATUS] = EP_NONE;
4529 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4530 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4531 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4534 if (ics_getting_history == H_GOT_REQ_HEADER ||
4535 ics_getting_history == H_GOT_UNREQ_HEADER) {
4536 /* This was an initial position from a move list, not
4537 the current position */
4541 /* Update currentMove and known move number limits */
4542 newMove = newGame || moveNum > forwardMostMove;
4545 forwardMostMove = backwardMostMove = currentMove = moveNum;
4546 if (gameMode == IcsExamining && moveNum == 0) {
4547 /* Workaround for ICS limitation: we are not told the wild
4548 type when starting to examine a game. But if we ask for
4549 the move list, the move list header will tell us */
4550 ics_getting_history = H_REQUESTED;
4551 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4554 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4555 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4557 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4558 /* [HGM] applied this also to an engine that is silently watching */
4559 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4560 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4561 gameInfo.variant == currentlyInitializedVariant) {
4562 takeback = forwardMostMove - moveNum;
4563 for (i = 0; i < takeback; i++) {
4564 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4565 SendToProgram("undo\n", &first);
4570 forwardMostMove = moveNum;
4571 if (!pausing || currentMove > forwardMostMove)
4572 currentMove = forwardMostMove;
4574 /* New part of history that is not contiguous with old part */
4575 if (pausing && gameMode == IcsExamining) {
4576 pauseExamInvalid = TRUE;
4577 forwardMostMove = pauseExamForwardMostMove;
4580 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4582 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4583 // [HGM] when we will receive the move list we now request, it will be
4584 // fed to the engine from the first move on. So if the engine is not
4585 // in the initial position now, bring it there.
4586 InitChessProgram(&first, 0);
4589 ics_getting_history = H_REQUESTED;
4590 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4593 forwardMostMove = backwardMostMove = currentMove = moveNum;
4596 /* Update the clocks */
4597 if (strchr(elapsed_time, '.')) {
4599 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4600 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4602 /* Time is in seconds */
4603 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4604 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4609 if (appData.zippyPlay && newGame &&
4610 gameMode != IcsObserving && gameMode != IcsIdle &&
4611 gameMode != IcsExamining)
4612 ZippyFirstBoard(moveNum, basetime, increment);
4615 /* Put the move on the move list, first converting
4616 to canonical algebraic form. */
4618 if (appData.debugMode) {
4619 if (appData.debugMode) { int f = forwardMostMove;
4620 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4621 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4622 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4624 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4625 fprintf(debugFP, "moveNum = %d\n", moveNum);
4626 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4627 setbuf(debugFP, NULL);
4629 if (moveNum <= backwardMostMove) {
4630 /* We don't know what the board looked like before
4632 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4633 strcat(parseList[moveNum - 1], " ");
4634 strcat(parseList[moveNum - 1], elapsed_time);
4635 moveList[moveNum - 1][0] = NULLCHAR;
4636 } else if (strcmp(move_str, "none") == 0) {
4637 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4638 /* Again, we don't know what the board looked like;
4639 this is really the start of the game. */
4640 parseList[moveNum - 1][0] = NULLCHAR;
4641 moveList[moveNum - 1][0] = NULLCHAR;
4642 backwardMostMove = moveNum;
4643 startedFromSetupPosition = TRUE;
4644 fromX = fromY = toX = toY = -1;
4646 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4647 // So we parse the long-algebraic move string in stead of the SAN move
4648 int valid; char buf[MSG_SIZ], *prom;
4650 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4651 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4652 // str looks something like "Q/a1-a2"; kill the slash
4654 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4655 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4656 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4657 strcat(buf, prom); // long move lacks promo specification!
4658 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4659 if(appData.debugMode)
4660 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4661 safeStrCpy(move_str, buf, MSG_SIZ);
4663 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4664 &fromX, &fromY, &toX, &toY, &promoChar)
4665 || ParseOneMove(buf, moveNum - 1, &moveType,
4666 &fromX, &fromY, &toX, &toY, &promoChar);
4667 // end of long SAN patch
4669 (void) CoordsToAlgebraic(boards[moveNum - 1],
4670 PosFlags(moveNum - 1),
4671 fromY, fromX, toY, toX, promoChar,
4672 parseList[moveNum-1]);
4673 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4679 if(gameInfo.variant != VariantShogi)
4680 strcat(parseList[moveNum - 1], "+");
4683 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4684 strcat(parseList[moveNum - 1], "#");
4687 strcat(parseList[moveNum - 1], " ");
4688 strcat(parseList[moveNum - 1], elapsed_time);
4689 /* currentMoveString is set as a side-effect of ParseOneMove */
4690 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4691 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4692 strcat(moveList[moveNum - 1], "\n");
4694 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4695 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4696 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4697 ChessSquare old, new = boards[moveNum][k][j];
4698 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4699 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4700 if(old == new) continue;
4701 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4702 else if(new == WhiteWazir || new == BlackWazir) {
4703 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4704 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4705 else boards[moveNum][k][j] = old; // preserve type of Gold
4706 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4707 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4710 /* Move from ICS was illegal!? Punt. */
4711 if (appData.debugMode) {
4712 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4713 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4715 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4716 strcat(parseList[moveNum - 1], " ");
4717 strcat(parseList[moveNum - 1], elapsed_time);
4718 moveList[moveNum - 1][0] = NULLCHAR;
4719 fromX = fromY = toX = toY = -1;
4722 if (appData.debugMode) {
4723 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4724 setbuf(debugFP, NULL);
4728 /* Send move to chess program (BEFORE animating it). */
4729 if (appData.zippyPlay && !newGame && newMove &&
4730 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4732 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4733 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4734 if (moveList[moveNum - 1][0] == NULLCHAR) {
4735 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4737 DisplayError(str, 0);
4739 if (first.sendTime) {
4740 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4742 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4743 if (firstMove && !bookHit) {
4745 if (first.useColors) {
4746 SendToProgram(gameMode == IcsPlayingWhite ?
4748 "black\ngo\n", &first);
4750 SendToProgram("go\n", &first);
4752 first.maybeThinking = TRUE;
4755 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4756 if (moveList[moveNum - 1][0] == NULLCHAR) {
4757 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4758 DisplayError(str, 0);
4760 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4761 SendMoveToProgram(moveNum - 1, &first);
4768 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4769 /* If move comes from a remote source, animate it. If it
4770 isn't remote, it will have already been animated. */
4771 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4772 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4774 if (!pausing && appData.highlightLastMove) {
4775 SetHighlights(fromX, fromY, toX, toY);
4779 /* Start the clocks */
4780 whiteFlag = blackFlag = FALSE;
4781 appData.clockMode = !(basetime == 0 && increment == 0);
4783 ics_clock_paused = TRUE;
4785 } else if (ticking == 1) {
4786 ics_clock_paused = FALSE;
4788 if (gameMode == IcsIdle ||
4789 relation == RELATION_OBSERVING_STATIC ||
4790 relation == RELATION_EXAMINING ||
4792 DisplayBothClocks();
4796 /* Display opponents and material strengths */
4797 if (gameInfo.variant != VariantBughouse &&
4798 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4799 if (tinyLayout || smallLayout) {
4800 if(gameInfo.variant == VariantNormal)
4801 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4802 gameInfo.white, white_stren, gameInfo.black, black_stren,
4803 basetime, increment);
4805 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4806 gameInfo.white, white_stren, gameInfo.black, black_stren,
4807 basetime, increment, (int) gameInfo.variant);
4809 if(gameInfo.variant == VariantNormal)
4810 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4811 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4812 basetime, increment);
4814 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4815 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4816 basetime, increment, VariantName(gameInfo.variant));
4819 if (appData.debugMode) {
4820 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4825 /* Display the board */
4826 if (!pausing && !appData.noGUI) {
4828 if (appData.premove)
4830 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4831 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4832 ClearPremoveHighlights();
4834 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4835 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4836 DrawPosition(j, boards[currentMove]);
4838 DisplayMove(moveNum - 1);
4839 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4840 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4841 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4842 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4846 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4848 if(bookHit) { // [HGM] book: simulate book reply
4849 static char bookMove[MSG_SIZ]; // a bit generous?
4851 programStats.nodes = programStats.depth = programStats.time =
4852 programStats.score = programStats.got_only_move = 0;
4853 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4855 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4856 strcat(bookMove, bookHit);
4857 HandleMachineMove(bookMove, &first);
4866 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4867 ics_getting_history = H_REQUESTED;
4868 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4874 AnalysisPeriodicEvent (int force)
4876 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4877 && !force) || !appData.periodicUpdates)
4880 /* Send . command to Crafty to collect stats */
4881 SendToProgram(".\n", &first);
4883 /* Don't send another until we get a response (this makes
4884 us stop sending to old Crafty's which don't understand
4885 the "." command (sending illegal cmds resets node count & time,
4886 which looks bad)) */
4887 programStats.ok_to_send = 0;
4891 ics_update_width (int new_width)
4893 ics_printf("set width %d\n", new_width);
4897 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4901 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4902 // null move in variant where engine does not understand it (for analysis purposes)
4903 SendBoard(cps, moveNum + 1); // send position after move in stead.
4906 if (cps->useUsermove) {
4907 SendToProgram("usermove ", cps);
4911 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4912 int len = space - parseList[moveNum];
4913 memcpy(buf, parseList[moveNum], len);
4915 buf[len] = NULLCHAR;
4917 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4919 SendToProgram(buf, cps);
4921 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4922 AlphaRank(moveList[moveNum], 4);
4923 SendToProgram(moveList[moveNum], cps);
4924 AlphaRank(moveList[moveNum], 4); // and back
4926 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4927 * the engine. It would be nice to have a better way to identify castle
4929 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4930 && cps->useOOCastle) {
4931 int fromX = moveList[moveNum][0] - AAA;
4932 int fromY = moveList[moveNum][1] - ONE;
4933 int toX = moveList[moveNum][2] - AAA;
4934 int toY = moveList[moveNum][3] - ONE;
4935 if((boards[moveNum][fromY][fromX] == WhiteKing
4936 && boards[moveNum][toY][toX] == WhiteRook)
4937 || (boards[moveNum][fromY][fromX] == BlackKing
4938 && boards[moveNum][toY][toX] == BlackRook)) {
4939 if(toX > fromX) SendToProgram("O-O\n", cps);
4940 else SendToProgram("O-O-O\n", cps);
4942 else SendToProgram(moveList[moveNum], cps);
4944 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4945 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4946 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4947 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4948 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4950 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4951 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952 SendToProgram(buf, cps);
4954 else SendToProgram(moveList[moveNum], cps);
4955 /* End of additions by Tord */
4958 /* [HGM] setting up the opening has brought engine in force mode! */
4959 /* Send 'go' if we are in a mode where machine should play. */
4960 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4961 (gameMode == TwoMachinesPlay ||
4963 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4965 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4966 SendToProgram("go\n", cps);
4967 if (appData.debugMode) {
4968 fprintf(debugFP, "(extra)\n");
4971 setboardSpoiledMachineBlack = 0;
4975 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4977 char user_move[MSG_SIZ];
4980 if(gameInfo.variant == VariantSChess && promoChar) {
4981 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4982 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4983 } else suffix[0] = NULLCHAR;
4987 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4988 (int)moveType, fromX, fromY, toX, toY);
4989 DisplayError(user_move + strlen("say "), 0);
4991 case WhiteKingSideCastle:
4992 case BlackKingSideCastle:
4993 case WhiteQueenSideCastleWild:
4994 case BlackQueenSideCastleWild:
4996 case WhiteHSideCastleFR:
4997 case BlackHSideCastleFR:
4999 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5001 case WhiteQueenSideCastle:
5002 case BlackQueenSideCastle:
5003 case WhiteKingSideCastleWild:
5004 case BlackKingSideCastleWild:
5006 case WhiteASideCastleFR:
5007 case BlackASideCastleFR:
5009 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5011 case WhiteNonPromotion:
5012 case BlackNonPromotion:
5013 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5015 case WhitePromotion:
5016 case BlackPromotion:
5017 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5018 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5019 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020 PieceToChar(WhiteFerz));
5021 else if(gameInfo.variant == VariantGreat)
5022 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5023 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5024 PieceToChar(WhiteMan));
5026 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5027 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5033 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5034 ToUpper(PieceToChar((ChessSquare) fromX)),
5035 AAA + toX, ONE + toY);
5037 case IllegalMove: /* could be a variant we don't quite understand */
5038 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5040 case WhiteCapturesEnPassant:
5041 case BlackCapturesEnPassant:
5042 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5043 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5046 SendToICS(user_move);
5047 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5048 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5053 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5054 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5055 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5056 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5057 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5060 if(gameMode != IcsExamining) { // is this ever not the case?
5061 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5063 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5064 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5065 } else { // on FICS we must first go to general examine mode
5066 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5068 if(gameInfo.variant != VariantNormal) {
5069 // try figure out wild number, as xboard names are not always valid on ICS
5070 for(i=1; i<=36; i++) {
5071 snprintf(buf, MSG_SIZ, "wild/%d", i);
5072 if(StringToVariant(buf) == gameInfo.variant) break;
5074 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5075 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5076 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5077 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5078 SendToICS(ics_prefix);
5080 if(startedFromSetupPosition || backwardMostMove != 0) {
5081 fen = PositionToFEN(backwardMostMove, NULL);
5082 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5083 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5085 } else { // FICS: everything has to set by separate bsetup commands
5086 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5087 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5089 if(!WhiteOnMove(backwardMostMove)) {
5090 SendToICS("bsetup tomove black\n");
5092 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5093 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5095 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5096 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5098 i = boards[backwardMostMove][EP_STATUS];
5099 if(i >= 0) { // set e.p.
5100 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5106 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5107 SendToICS("bsetup done\n"); // switch to normal examining.
5109 for(i = backwardMostMove; i<last; i++) {
5111 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5114 SendToICS(ics_prefix);
5115 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5119 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5121 if (rf == DROP_RANK) {
5122 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5123 sprintf(move, "%c@%c%c\n",
5124 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5126 if (promoChar == 'x' || promoChar == NULLCHAR) {
5127 sprintf(move, "%c%c%c%c\n",
5128 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5130 sprintf(move, "%c%c%c%c%c\n",
5131 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5137 ProcessICSInitScript (FILE *f)
5141 while (fgets(buf, MSG_SIZ, f)) {
5142 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5149 static int lastX, lastY, selectFlag, dragging;
5154 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5155 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5156 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5157 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5158 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5159 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5162 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5163 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5164 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5165 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5166 if(!step) step = -1;
5167 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5168 appData.testLegality && (promoSweep == king ||
5169 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5170 ChangeDragPiece(promoSweep);
5174 PromoScroll (int x, int y)
5178 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5179 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5180 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5181 if(!step) return FALSE;
5182 lastX = x; lastY = y;
5183 if((promoSweep < BlackPawn) == flipView) step = -step;
5184 if(step > 0) selectFlag = 1;
5185 if(!selectFlag) Sweep(step);
5190 NextPiece (int step)
5192 ChessSquare piece = boards[currentMove][toY][toX];
5195 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5196 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5197 if(!step) step = -1;
5198 } while(PieceToChar(pieceSweep) == '.');
5199 boards[currentMove][toY][toX] = pieceSweep;
5200 DrawPosition(FALSE, boards[currentMove]);
5201 boards[currentMove][toY][toX] = piece;
5203 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5205 AlphaRank (char *move, int n)
5207 // char *p = move, c; int x, y;
5209 if (appData.debugMode) {
5210 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5214 move[2]>='0' && move[2]<='9' &&
5215 move[3]>='a' && move[3]<='x' ) {
5217 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5218 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5220 if(move[0]>='0' && move[0]<='9' &&
5221 move[1]>='a' && move[1]<='x' &&
5222 move[2]>='0' && move[2]<='9' &&
5223 move[3]>='a' && move[3]<='x' ) {
5224 /* input move, Shogi -> normal */
5225 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5226 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5227 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5228 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5231 move[3]>='0' && move[3]<='9' &&
5232 move[2]>='a' && move[2]<='x' ) {
5234 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5235 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5238 move[0]>='a' && move[0]<='x' &&
5239 move[3]>='0' && move[3]<='9' &&
5240 move[2]>='a' && move[2]<='x' ) {
5241 /* output move, normal -> Shogi */
5242 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5243 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5244 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5245 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5246 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5248 if (appData.debugMode) {
5249 fprintf(debugFP, " out = '%s'\n", move);
5253 char yy_textstr[8000];
5255 /* Parser for moves from gnuchess, ICS, or user typein box */
5257 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5259 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5261 switch (*moveType) {
5262 case WhitePromotion:
5263 case BlackPromotion:
5264 case WhiteNonPromotion:
5265 case BlackNonPromotion:
5267 case WhiteCapturesEnPassant:
5268 case BlackCapturesEnPassant:
5269 case WhiteKingSideCastle:
5270 case WhiteQueenSideCastle:
5271 case BlackKingSideCastle:
5272 case BlackQueenSideCastle:
5273 case WhiteKingSideCastleWild:
5274 case WhiteQueenSideCastleWild:
5275 case BlackKingSideCastleWild:
5276 case BlackQueenSideCastleWild:
5277 /* Code added by Tord: */
5278 case WhiteHSideCastleFR:
5279 case WhiteASideCastleFR:
5280 case BlackHSideCastleFR:
5281 case BlackASideCastleFR:
5282 /* End of code added by Tord */
5283 case IllegalMove: /* bug or odd chess variant */
5284 *fromX = currentMoveString[0] - AAA;
5285 *fromY = currentMoveString[1] - ONE;
5286 *toX = currentMoveString[2] - AAA;
5287 *toY = currentMoveString[3] - ONE;
5288 *promoChar = currentMoveString[4];
5289 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5290 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5291 if (appData.debugMode) {
5292 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5294 *fromX = *fromY = *toX = *toY = 0;
5297 if (appData.testLegality) {
5298 return (*moveType != IllegalMove);
5300 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5301 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5306 *fromX = *moveType == WhiteDrop ?
5307 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5308 (int) CharToPiece(ToLower(currentMoveString[0]));
5310 *toX = currentMoveString[2] - AAA;
5311 *toY = currentMoveString[3] - ONE;
5312 *promoChar = NULLCHAR;
5316 case ImpossibleMove:
5326 if (appData.debugMode) {
5327 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5330 *fromX = *fromY = *toX = *toY = 0;
5331 *promoChar = NULLCHAR;
5336 Boolean pushed = FALSE;
5337 char *lastParseAttempt;
5340 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5341 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5342 int fromX, fromY, toX, toY; char promoChar;
5347 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5348 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5351 endPV = forwardMostMove;
5353 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5354 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5355 lastParseAttempt = pv;
5356 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5357 if(!valid && nr == 0 &&
5358 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5359 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5360 // Hande case where played move is different from leading PV move
5361 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5362 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5363 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5364 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5365 endPV += 2; // if position different, keep this
5366 moveList[endPV-1][0] = fromX + AAA;
5367 moveList[endPV-1][1] = fromY + ONE;
5368 moveList[endPV-1][2] = toX + AAA;
5369 moveList[endPV-1][3] = toY + ONE;
5370 parseList[endPV-1][0] = NULLCHAR;
5371 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5374 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5375 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5376 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5377 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5378 valid++; // allow comments in PV
5382 if(endPV+1 > framePtr) break; // no space, truncate
5385 CopyBoard(boards[endPV], boards[endPV-1]);
5386 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5387 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5388 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5389 CoordsToAlgebraic(boards[endPV - 1],
5390 PosFlags(endPV - 1),
5391 fromY, fromX, toY, toX, promoChar,
5392 parseList[endPV - 1]);
5394 if(atEnd == 2) return; // used hidden, for PV conversion
5395 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5396 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5397 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5398 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5399 DrawPosition(TRUE, boards[currentMove]);
5403 MultiPV (ChessProgramState *cps)
5404 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5406 for(i=0; i<cps->nrOptions; i++)
5407 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5413 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5415 int startPV, multi, lineStart, origIndex = index;
5416 char *p, buf2[MSG_SIZ];
5418 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5419 lastX = x; lastY = y;
5420 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5421 lineStart = startPV = index;
5422 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5423 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5425 do{ while(buf[index] && buf[index] != '\n') index++;
5426 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5428 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5429 int n = first.option[multi].value;
5430 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5431 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5432 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5433 first.option[multi].value = n;
5436 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5437 ExcludeClick(origIndex - lineStart);
5440 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5441 *start = startPV; *end = index-1;
5448 static char buf[10*MSG_SIZ];
5449 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5451 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5452 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5453 for(i = forwardMostMove; i<endPV; i++){
5454 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5455 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5458 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5459 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5465 LoadPV (int x, int y)
5466 { // called on right mouse click to load PV
5467 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5468 lastX = x; lastY = y;
5469 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5476 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5477 if(endPV < 0) return;
5478 if(appData.autoCopyPV) CopyFENToClipboard();
5480 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5481 Boolean saveAnimate = appData.animate;
5483 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5484 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5485 } else storedGames--; // abandon shelved tail of original game
5488 forwardMostMove = currentMove;
5489 currentMove = oldFMM;
5490 appData.animate = FALSE;
5491 ToNrEvent(forwardMostMove);
5492 appData.animate = saveAnimate;
5494 currentMove = forwardMostMove;
5495 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5496 ClearPremoveHighlights();
5497 DrawPosition(TRUE, boards[currentMove]);
5501 MovePV (int x, int y, int h)
5502 { // step through PV based on mouse coordinates (called on mouse move)
5503 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5505 // we must somehow check if right button is still down (might be released off board!)
5506 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5507 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5508 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5510 lastX = x; lastY = y;
5512 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5513 if(endPV < 0) return;
5514 if(y < margin) step = 1; else
5515 if(y > h - margin) step = -1;
5516 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5517 currentMove += step;
5518 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5519 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5520 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5521 DrawPosition(FALSE, boards[currentMove]);
5525 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5526 // All positions will have equal probability, but the current method will not provide a unique
5527 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5533 int piecesLeft[(int)BlackPawn];
5534 int seed, nrOfShuffles;
5537 GetPositionNumber ()
5538 { // sets global variable seed
5541 seed = appData.defaultFrcPosition;
5542 if(seed < 0) { // randomize based on time for negative FRC position numbers
5543 for(i=0; i<50; i++) seed += random();
5544 seed = random() ^ random() >> 8 ^ random() << 8;
5545 if(seed<0) seed = -seed;
5550 put (Board board, int pieceType, int rank, int n, int shade)
5551 // put the piece on the (n-1)-th empty squares of the given shade
5555 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5556 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5557 board[rank][i] = (ChessSquare) pieceType;
5558 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5560 piecesLeft[pieceType]--;
5569 AddOnePiece (Board board, int pieceType, int rank, int shade)
5570 // calculate where the next piece goes, (any empty square), and put it there
5574 i = seed % squaresLeft[shade];
5575 nrOfShuffles *= squaresLeft[shade];
5576 seed /= squaresLeft[shade];
5577 put(board, pieceType, rank, i, shade);
5581 AddTwoPieces (Board board, int pieceType, int rank)
5582 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5584 int i, n=squaresLeft[ANY], j=n-1, k;
5586 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5587 i = seed % k; // pick one
5590 while(i >= j) i -= j--;
5591 j = n - 1 - j; i += j;
5592 put(board, pieceType, rank, j, ANY);
5593 put(board, pieceType, rank, i, ANY);
5597 SetUpShuffle (Board board, int number)
5601 GetPositionNumber(); nrOfShuffles = 1;
5603 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5604 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5605 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5607 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5609 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5610 p = (int) board[0][i];
5611 if(p < (int) BlackPawn) piecesLeft[p] ++;
5612 board[0][i] = EmptySquare;
5615 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5616 // shuffles restricted to allow normal castling put KRR first
5617 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5618 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5619 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5620 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5621 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5622 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5623 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5624 put(board, WhiteRook, 0, 0, ANY);
5625 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5628 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5629 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5630 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5631 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5632 while(piecesLeft[p] >= 2) {
5633 AddOnePiece(board, p, 0, LITE);
5634 AddOnePiece(board, p, 0, DARK);
5636 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5639 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5640 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5641 // but we leave King and Rooks for last, to possibly obey FRC restriction
5642 if(p == (int)WhiteRook) continue;
5643 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5644 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5647 // now everything is placed, except perhaps King (Unicorn) and Rooks
5649 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5650 // Last King gets castling rights
5651 while(piecesLeft[(int)WhiteUnicorn]) {
5652 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5653 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5656 while(piecesLeft[(int)WhiteKing]) {
5657 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5658 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5663 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5664 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5667 // Only Rooks can be left; simply place them all
5668 while(piecesLeft[(int)WhiteRook]) {
5669 i = put(board, WhiteRook, 0, 0, ANY);
5670 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5673 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5675 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5678 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5679 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5682 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5686 SetCharTable (char *table, const char * map)
5687 /* [HGM] moved here from winboard.c because of its general usefulness */
5688 /* Basically a safe strcpy that uses the last character as King */
5690 int result = FALSE; int NrPieces;
5692 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5693 && NrPieces >= 12 && !(NrPieces&1)) {
5694 int i; /* [HGM] Accept even length from 12 to 34 */
5696 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5697 for( i=0; i<NrPieces/2-1; i++ ) {
5699 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5701 table[(int) WhiteKing] = map[NrPieces/2-1];
5702 table[(int) BlackKing] = map[NrPieces-1];
5711 Prelude (Board board)
5712 { // [HGM] superchess: random selection of exo-pieces
5713 int i, j, k; ChessSquare p;
5714 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5716 GetPositionNumber(); // use FRC position number
5718 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5719 SetCharTable(pieceToChar, appData.pieceToCharTable);
5720 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5721 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5724 j = seed%4; seed /= 4;
5725 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5726 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5727 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5728 j = seed%3 + (seed%3 >= j); seed /= 3;
5729 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5730 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5731 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5732 j = seed%3; seed /= 3;
5733 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5734 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5735 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5736 j = seed%2 + (seed%2 >= j); seed /= 2;
5737 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5741 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5742 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5743 put(board, exoPieces[0], 0, 0, ANY);
5744 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5748 InitPosition (int redraw)
5750 ChessSquare (* pieces)[BOARD_FILES];
5751 int i, j, pawnRow, overrule,
5752 oldx = gameInfo.boardWidth,
5753 oldy = gameInfo.boardHeight,
5754 oldh = gameInfo.holdingsWidth;
5757 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5759 /* [AS] Initialize pv info list [HGM] and game status */
5761 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5762 pvInfoList[i].depth = 0;
5763 boards[i][EP_STATUS] = EP_NONE;
5764 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5767 initialRulePlies = 0; /* 50-move counter start */
5769 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5770 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5774 /* [HGM] logic here is completely changed. In stead of full positions */
5775 /* the initialized data only consist of the two backranks. The switch */
5776 /* selects which one we will use, which is than copied to the Board */
5777 /* initialPosition, which for the rest is initialized by Pawns and */
5778 /* empty squares. This initial position is then copied to boards[0], */
5779 /* possibly after shuffling, so that it remains available. */
5781 gameInfo.holdingsWidth = 0; /* default board sizes */
5782 gameInfo.boardWidth = 8;
5783 gameInfo.boardHeight = 8;
5784 gameInfo.holdingsSize = 0;
5785 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5786 for(i=0; i<BOARD_FILES-2; i++)
5787 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5788 initialPosition[EP_STATUS] = EP_NONE;
5789 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5790 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5791 SetCharTable(pieceNickName, appData.pieceNickNames);
5792 else SetCharTable(pieceNickName, "............");
5795 switch (gameInfo.variant) {
5796 case VariantFischeRandom:
5797 shuffleOpenings = TRUE;
5800 case VariantShatranj:
5801 pieces = ShatranjArray;
5802 nrCastlingRights = 0;
5803 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5806 pieces = makrukArray;
5807 nrCastlingRights = 0;
5808 startedFromSetupPosition = TRUE;
5809 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5811 case VariantTwoKings:
5812 pieces = twoKingsArray;
5815 pieces = GrandArray;
5816 nrCastlingRights = 0;
5817 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5818 gameInfo.boardWidth = 10;
5819 gameInfo.boardHeight = 10;
5820 gameInfo.holdingsSize = 7;
5822 case VariantCapaRandom:
5823 shuffleOpenings = TRUE;
5824 case VariantCapablanca:
5825 pieces = CapablancaArray;
5826 gameInfo.boardWidth = 10;
5827 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5830 pieces = GothicArray;
5831 gameInfo.boardWidth = 10;
5832 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5835 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5836 gameInfo.holdingsSize = 7;
5839 pieces = JanusArray;
5840 gameInfo.boardWidth = 10;
5841 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5842 nrCastlingRights = 6;
5843 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5844 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5845 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5846 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5847 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5848 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5851 pieces = FalconArray;
5852 gameInfo.boardWidth = 10;
5853 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5855 case VariantXiangqi:
5856 pieces = XiangqiArray;
5857 gameInfo.boardWidth = 9;
5858 gameInfo.boardHeight = 10;
5859 nrCastlingRights = 0;
5860 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5863 pieces = ShogiArray;
5864 gameInfo.boardWidth = 9;
5865 gameInfo.boardHeight = 9;
5866 gameInfo.holdingsSize = 7;
5867 nrCastlingRights = 0;
5868 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5870 case VariantCourier:
5871 pieces = CourierArray;
5872 gameInfo.boardWidth = 12;
5873 nrCastlingRights = 0;
5874 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5876 case VariantKnightmate:
5877 pieces = KnightmateArray;
5878 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5880 case VariantSpartan:
5881 pieces = SpartanArray;
5882 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5885 pieces = fairyArray;
5886 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5889 pieces = GreatArray;
5890 gameInfo.boardWidth = 10;
5891 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5892 gameInfo.holdingsSize = 8;
5896 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5897 gameInfo.holdingsSize = 8;
5898 startedFromSetupPosition = TRUE;
5900 case VariantCrazyhouse:
5901 case VariantBughouse:
5903 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5904 gameInfo.holdingsSize = 5;
5906 case VariantWildCastle:
5908 /* !!?shuffle with kings guaranteed to be on d or e file */
5909 shuffleOpenings = 1;
5911 case VariantNoCastle:
5913 nrCastlingRights = 0;
5914 /* !!?unconstrained back-rank shuffle */
5915 shuffleOpenings = 1;
5920 if(appData.NrFiles >= 0) {
5921 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5922 gameInfo.boardWidth = appData.NrFiles;
5924 if(appData.NrRanks >= 0) {
5925 gameInfo.boardHeight = appData.NrRanks;
5927 if(appData.holdingsSize >= 0) {
5928 i = appData.holdingsSize;
5929 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5930 gameInfo.holdingsSize = i;
5932 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5933 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5934 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5936 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5937 if(pawnRow < 1) pawnRow = 1;
5938 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5940 /* User pieceToChar list overrules defaults */
5941 if(appData.pieceToCharTable != NULL)
5942 SetCharTable(pieceToChar, appData.pieceToCharTable);
5944 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5946 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5947 s = (ChessSquare) 0; /* account holding counts in guard band */
5948 for( i=0; i<BOARD_HEIGHT; i++ )
5949 initialPosition[i][j] = s;
5951 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5952 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5953 initialPosition[pawnRow][j] = WhitePawn;
5954 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5955 if(gameInfo.variant == VariantXiangqi) {
5957 initialPosition[pawnRow][j] =
5958 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5959 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5960 initialPosition[2][j] = WhiteCannon;
5961 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5965 if(gameInfo.variant == VariantGrand) {
5966 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5967 initialPosition[0][j] = WhiteRook;
5968 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5971 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5973 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5976 initialPosition[1][j] = WhiteBishop;
5977 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5979 initialPosition[1][j] = WhiteRook;
5980 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5983 if( nrCastlingRights == -1) {
5984 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5985 /* This sets default castling rights from none to normal corners */
5986 /* Variants with other castling rights must set them themselves above */
5987 nrCastlingRights = 6;
5989 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5990 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5991 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5992 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5993 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5994 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5997 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5998 if(gameInfo.variant == VariantGreat) { // promotion commoners
5999 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6000 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6001 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6002 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6004 if( gameInfo.variant == VariantSChess ) {
6005 initialPosition[1][0] = BlackMarshall;
6006 initialPosition[2][0] = BlackAngel;
6007 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6008 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6009 initialPosition[1][1] = initialPosition[2][1] =
6010 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6012 if (appData.debugMode) {
6013 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6015 if(shuffleOpenings) {
6016 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6017 startedFromSetupPosition = TRUE;
6019 if(startedFromPositionFile) {
6020 /* [HGM] loadPos: use PositionFile for every new game */
6021 CopyBoard(initialPosition, filePosition);
6022 for(i=0; i<nrCastlingRights; i++)
6023 initialRights[i] = filePosition[CASTLING][i];
6024 startedFromSetupPosition = TRUE;
6027 CopyBoard(boards[0], initialPosition);
6029 if(oldx != gameInfo.boardWidth ||
6030 oldy != gameInfo.boardHeight ||
6031 oldv != gameInfo.variant ||
6032 oldh != gameInfo.holdingsWidth
6034 InitDrawingSizes(-2 ,0);
6036 oldv = gameInfo.variant;
6038 DrawPosition(TRUE, boards[currentMove]);
6042 SendBoard (ChessProgramState *cps, int moveNum)
6044 char message[MSG_SIZ];
6046 if (cps->useSetboard) {
6047 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6048 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6049 SendToProgram(message, cps);
6054 int i, j, left=0, right=BOARD_WIDTH;
6055 /* Kludge to set black to move, avoiding the troublesome and now
6056 * deprecated "black" command.
6058 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6059 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6061 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6063 SendToProgram("edit\n", cps);
6064 SendToProgram("#\n", cps);
6065 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6066 bp = &boards[moveNum][i][left];
6067 for (j = left; j < right; j++, bp++) {
6068 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6069 if ((int) *bp < (int) BlackPawn) {
6070 if(j == BOARD_RGHT+1)
6071 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6072 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6073 if(message[0] == '+' || message[0] == '~') {
6074 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6075 PieceToChar((ChessSquare)(DEMOTED *bp)),
6078 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6079 message[1] = BOARD_RGHT - 1 - j + '1';
6080 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082 SendToProgram(message, cps);
6087 SendToProgram("c\n", cps);
6088 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6089 bp = &boards[moveNum][i][left];
6090 for (j = left; j < right; j++, bp++) {
6091 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6092 if (((int) *bp != (int) EmptySquare)
6093 && ((int) *bp >= (int) BlackPawn)) {
6094 if(j == BOARD_LEFT-2)
6095 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6096 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6098 if(message[0] == '+' || message[0] == '~') {
6099 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6100 PieceToChar((ChessSquare)(DEMOTED *bp)),
6103 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6104 message[1] = BOARD_RGHT - 1 - j + '1';
6105 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6107 SendToProgram(message, cps);
6112 SendToProgram(".\n", cps);
6114 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6117 char exclusionHeader[MSG_SIZ];
6118 int exCnt, excludePtr;
6119 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6120 static Exclusion excluTab[200];
6121 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6127 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6128 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6134 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6135 excludePtr = 24; exCnt = 0;
6140 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6141 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6142 char buf[2*MOVE_LEN], *p;
6143 Exclusion *e = excluTab;
6145 for(i=0; i<exCnt; i++)
6146 if(e[i].ff == fromX && e[i].fr == fromY &&
6147 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6148 if(i == exCnt) { // was not in exclude list; add it
6149 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6150 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6151 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6154 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6155 excludePtr++; e[i].mark = excludePtr++;
6156 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6159 exclusionHeader[e[i].mark] = state;
6163 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6164 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6168 if(promoChar == -1) { // kludge to indicate best move
6169 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6170 return 1; // if unparsable, abort
6172 // update exclusion map (resolving toggle by consulting existing state)
6173 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6175 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6176 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6177 excludeMap[k] |= 1<<j;
6178 else excludeMap[k] &= ~(1<<j);
6180 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6182 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6183 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6184 SendToProgram(buf, &first);
6185 return (state == '+');
6189 ExcludeClick (int index)
6192 Exclusion *e = excluTab;
6193 if(index < 25) { // none, best or tail clicked
6194 if(index < 13) { // none: include all
6195 WriteMap(0); // clear map
6196 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6197 SendToProgram("include all\n", &first); // and inform engine
6198 } else if(index > 18) { // tail
6199 if(exclusionHeader[19] == '-') { // tail was excluded
6200 SendToProgram("include all\n", &first);
6201 WriteMap(0); // clear map completely
6202 // now re-exclude selected moves
6203 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6204 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6205 } else { // tail was included or in mixed state
6206 SendToProgram("exclude all\n", &first);
6207 WriteMap(0xFF); // fill map completely
6208 // now re-include selected moves
6209 j = 0; // count them
6210 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6211 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6212 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6215 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6218 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6219 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6220 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6227 DefaultPromoChoice (int white)
6230 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6231 result = WhiteFerz; // no choice
6232 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6233 result= WhiteKing; // in Suicide Q is the last thing we want
6234 else if(gameInfo.variant == VariantSpartan)
6235 result = white ? WhiteQueen : WhiteAngel;
6236 else result = WhiteQueen;
6237 if(!white) result = WHITE_TO_BLACK result;
6241 static int autoQueen; // [HGM] oneclick
6244 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6246 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6247 /* [HGM] add Shogi promotions */
6248 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6253 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6254 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6256 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6257 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6260 piece = boards[currentMove][fromY][fromX];
6261 if(gameInfo.variant == VariantShogi) {
6262 promotionZoneSize = BOARD_HEIGHT/3;
6263 highestPromotingPiece = (int)WhiteFerz;
6264 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6265 promotionZoneSize = 3;
6268 // Treat Lance as Pawn when it is not representing Amazon
6269 if(gameInfo.variant != VariantSuper) {
6270 if(piece == WhiteLance) piece = WhitePawn; else
6271 if(piece == BlackLance) piece = BlackPawn;
6274 // next weed out all moves that do not touch the promotion zone at all
6275 if((int)piece >= BlackPawn) {
6276 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6278 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6280 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6281 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6284 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6286 // weed out mandatory Shogi promotions
6287 if(gameInfo.variant == VariantShogi) {
6288 if(piece >= BlackPawn) {
6289 if(toY == 0 && piece == BlackPawn ||
6290 toY == 0 && piece == BlackQueen ||
6291 toY <= 1 && piece == BlackKnight) {
6296 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6297 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6298 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6305 // weed out obviously illegal Pawn moves
6306 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6307 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6308 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6309 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6310 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6311 // note we are not allowed to test for valid (non-)capture, due to premove
6314 // we either have a choice what to promote to, or (in Shogi) whether to promote
6315 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6316 *promoChoice = PieceToChar(BlackFerz); // no choice
6319 // no sense asking what we must promote to if it is going to explode...
6320 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6321 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6324 // give caller the default choice even if we will not make it
6325 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6326 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6327 if( sweepSelect && gameInfo.variant != VariantGreat
6328 && gameInfo.variant != VariantGrand
6329 && gameInfo.variant != VariantSuper) return FALSE;
6330 if(autoQueen) return FALSE; // predetermined
6332 // suppress promotion popup on illegal moves that are not premoves
6333 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6334 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6335 if(appData.testLegality && !premove) {
6336 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6337 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6338 if(moveType != WhitePromotion && moveType != BlackPromotion)
6346 InPalace (int row, int column)
6347 { /* [HGM] for Xiangqi */
6348 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6349 column < (BOARD_WIDTH + 4)/2 &&
6350 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6355 PieceForSquare (int x, int y)
6357 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6360 return boards[currentMove][y][x];
6364 OKToStartUserMove (int x, int y)
6366 ChessSquare from_piece;
6369 if (matchMode) return FALSE;
6370 if (gameMode == EditPosition) return TRUE;
6372 if (x >= 0 && y >= 0)
6373 from_piece = boards[currentMove][y][x];
6375 from_piece = EmptySquare;
6377 if (from_piece == EmptySquare) return FALSE;
6379 white_piece = (int)from_piece >= (int)WhitePawn &&
6380 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6384 case TwoMachinesPlay:
6392 case MachinePlaysWhite:
6393 case IcsPlayingBlack:
6394 if (appData.zippyPlay) return FALSE;
6396 DisplayMoveError(_("You are playing Black"));
6401 case MachinePlaysBlack:
6402 case IcsPlayingWhite:
6403 if (appData.zippyPlay) return FALSE;
6405 DisplayMoveError(_("You are playing White"));
6410 case PlayFromGameFile:
6411 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6413 if (!white_piece && WhiteOnMove(currentMove)) {
6414 DisplayMoveError(_("It is White's turn"));
6417 if (white_piece && !WhiteOnMove(currentMove)) {
6418 DisplayMoveError(_("It is Black's turn"));
6421 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6422 /* Editing correspondence game history */
6423 /* Could disallow this or prompt for confirmation */
6428 case BeginningOfGame:
6429 if (appData.icsActive) return FALSE;
6430 if (!appData.noChessProgram) {
6432 DisplayMoveError(_("You are playing White"));
6439 if (!white_piece && WhiteOnMove(currentMove)) {
6440 DisplayMoveError(_("It is White's turn"));
6443 if (white_piece && !WhiteOnMove(currentMove)) {
6444 DisplayMoveError(_("It is Black's turn"));
6453 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6454 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6455 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6456 && gameMode != AnalyzeFile && gameMode != Training) {
6457 DisplayMoveError(_("Displayed position is not current"));
6464 OnlyMove (int *x, int *y, Boolean captures)
6466 DisambiguateClosure cl;
6467 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6469 case MachinePlaysBlack:
6470 case IcsPlayingWhite:
6471 case BeginningOfGame:
6472 if(!WhiteOnMove(currentMove)) return FALSE;
6474 case MachinePlaysWhite:
6475 case IcsPlayingBlack:
6476 if(WhiteOnMove(currentMove)) return FALSE;
6483 cl.pieceIn = EmptySquare;
6488 cl.promoCharIn = NULLCHAR;
6489 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6490 if( cl.kind == NormalMove ||
6491 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6492 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6493 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6500 if(cl.kind != ImpossibleMove) return FALSE;
6501 cl.pieceIn = EmptySquare;
6506 cl.promoCharIn = NULLCHAR;
6507 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6508 if( cl.kind == NormalMove ||
6509 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6510 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6511 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6516 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6522 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6523 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6524 int lastLoadGameUseList = FALSE;
6525 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6526 ChessMove lastLoadGameStart = EndOfFile;
6530 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6533 ChessSquare pdown, pup;
6534 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6537 /* Check if the user is playing in turn. This is complicated because we
6538 let the user "pick up" a piece before it is his turn. So the piece he
6539 tried to pick up may have been captured by the time he puts it down!
6540 Therefore we use the color the user is supposed to be playing in this
6541 test, not the color of the piece that is currently on the starting
6542 square---except in EditGame mode, where the user is playing both
6543 sides; fortunately there the capture race can't happen. (It can
6544 now happen in IcsExamining mode, but that's just too bad. The user
6545 will get a somewhat confusing message in that case.)
6550 case TwoMachinesPlay:
6554 /* We switched into a game mode where moves are not accepted,
6555 perhaps while the mouse button was down. */
6558 case MachinePlaysWhite:
6559 /* User is moving for Black */
6560 if (WhiteOnMove(currentMove)) {
6561 DisplayMoveError(_("It is White's turn"));
6566 case MachinePlaysBlack:
6567 /* User is moving for White */
6568 if (!WhiteOnMove(currentMove)) {
6569 DisplayMoveError(_("It is Black's turn"));
6574 case PlayFromGameFile:
6575 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6578 case BeginningOfGame:
6581 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6582 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6583 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6584 /* User is moving for Black */
6585 if (WhiteOnMove(currentMove)) {
6586 DisplayMoveError(_("It is White's turn"));
6590 /* User is moving for White */
6591 if (!WhiteOnMove(currentMove)) {
6592 DisplayMoveError(_("It is Black's turn"));
6598 case IcsPlayingBlack:
6599 /* User is moving for Black */
6600 if (WhiteOnMove(currentMove)) {
6601 if (!appData.premove) {
6602 DisplayMoveError(_("It is White's turn"));
6603 } else if (toX >= 0 && toY >= 0) {
6606 premoveFromX = fromX;
6607 premoveFromY = fromY;
6608 premovePromoChar = promoChar;
6610 if (appData.debugMode)
6611 fprintf(debugFP, "Got premove: fromX %d,"
6612 "fromY %d, toX %d, toY %d\n",
6613 fromX, fromY, toX, toY);
6619 case IcsPlayingWhite:
6620 /* User is moving for White */
6621 if (!WhiteOnMove(currentMove)) {
6622 if (!appData.premove) {
6623 DisplayMoveError(_("It is Black's turn"));
6624 } else if (toX >= 0 && toY >= 0) {
6627 premoveFromX = fromX;
6628 premoveFromY = fromY;
6629 premovePromoChar = promoChar;
6631 if (appData.debugMode)
6632 fprintf(debugFP, "Got premove: fromX %d,"
6633 "fromY %d, toX %d, toY %d\n",
6634 fromX, fromY, toX, toY);
6644 /* EditPosition, empty square, or different color piece;
6645 click-click move is possible */
6646 if (toX == -2 || toY == -2) {
6647 boards[0][fromY][fromX] = EmptySquare;
6648 DrawPosition(FALSE, boards[currentMove]);
6650 } else if (toX >= 0 && toY >= 0) {
6651 boards[0][toY][toX] = boards[0][fromY][fromX];
6652 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6653 if(boards[0][fromY][0] != EmptySquare) {
6654 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6655 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6658 if(fromX == BOARD_RGHT+1) {
6659 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6660 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6661 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6664 boards[0][fromY][fromX] = EmptySquare;
6665 DrawPosition(FALSE, boards[currentMove]);
6671 if(toX < 0 || toY < 0) return;
6672 pdown = boards[currentMove][fromY][fromX];
6673 pup = boards[currentMove][toY][toX];
6675 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6676 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6677 if( pup != EmptySquare ) return;
6678 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6679 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6680 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6681 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6682 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6683 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6684 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6688 /* [HGM] always test for legality, to get promotion info */
6689 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6690 fromY, fromX, toY, toX, promoChar);
6692 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6694 /* [HGM] but possibly ignore an IllegalMove result */
6695 if (appData.testLegality) {
6696 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6697 DisplayMoveError(_("Illegal move"));
6702 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6703 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6704 ClearPremoveHighlights(); // was included
6705 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6709 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6712 /* Common tail of UserMoveEvent and DropMenuEvent */
6714 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6718 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6719 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6720 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6721 if(WhiteOnMove(currentMove)) {
6722 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6724 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6728 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6729 move type in caller when we know the move is a legal promotion */
6730 if(moveType == NormalMove && promoChar)
6731 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6733 /* [HGM] <popupFix> The following if has been moved here from
6734 UserMoveEvent(). Because it seemed to belong here (why not allow
6735 piece drops in training games?), and because it can only be
6736 performed after it is known to what we promote. */
6737 if (gameMode == Training) {
6738 /* compare the move played on the board to the next move in the
6739 * game. If they match, display the move and the opponent's response.
6740 * If they don't match, display an error message.
6744 CopyBoard(testBoard, boards[currentMove]);
6745 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6747 if (CompareBoards(testBoard, boards[currentMove+1])) {
6748 ForwardInner(currentMove+1);
6750 /* Autoplay the opponent's response.
6751 * if appData.animate was TRUE when Training mode was entered,
6752 * the response will be animated.
6754 saveAnimate = appData.animate;
6755 appData.animate = animateTraining;
6756 ForwardInner(currentMove+1);
6757 appData.animate = saveAnimate;
6759 /* check for the end of the game */
6760 if (currentMove >= forwardMostMove) {
6761 gameMode = PlayFromGameFile;
6763 SetTrainingModeOff();
6764 DisplayInformation(_("End of game"));
6767 DisplayError(_("Incorrect move"), 0);
6772 /* Ok, now we know that the move is good, so we can kill
6773 the previous line in Analysis Mode */
6774 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6775 && currentMove < forwardMostMove) {
6776 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6777 else forwardMostMove = currentMove;
6782 /* If we need the chess program but it's dead, restart it */
6783 ResurrectChessProgram();
6785 /* A user move restarts a paused game*/
6789 thinkOutput[0] = NULLCHAR;
6791 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6793 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6794 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6798 if (gameMode == BeginningOfGame) {
6799 if (appData.noChessProgram) {
6800 gameMode = EditGame;
6804 gameMode = MachinePlaysBlack;
6807 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6809 if (first.sendName) {
6810 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6811 SendToProgram(buf, &first);
6818 /* Relay move to ICS or chess engine */
6819 if (appData.icsActive) {
6820 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6821 gameMode == IcsExamining) {
6822 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6823 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6825 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6827 // also send plain move, in case ICS does not understand atomic claims
6828 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832 if (first.sendTime && (gameMode == BeginningOfGame ||
6833 gameMode == MachinePlaysWhite ||
6834 gameMode == MachinePlaysBlack)) {
6835 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6837 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6838 // [HGM] book: if program might be playing, let it use book
6839 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6840 first.maybeThinking = TRUE;
6841 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6842 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6843 SendBoard(&first, currentMove+1);
6844 } else SendMoveToProgram(forwardMostMove-1, &first);
6845 if (currentMove == cmailOldMove + 1) {
6846 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6850 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6854 if(appData.testLegality)
6855 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6861 if (WhiteOnMove(currentMove)) {
6862 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6864 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6868 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6873 case MachinePlaysBlack:
6874 case MachinePlaysWhite:
6875 /* disable certain menu options while machine is thinking */
6876 SetMachineThinkingEnables();
6883 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6884 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6886 if(bookHit) { // [HGM] book: simulate book reply
6887 static char bookMove[MSG_SIZ]; // a bit generous?
6889 programStats.nodes = programStats.depth = programStats.time =
6890 programStats.score = programStats.got_only_move = 0;
6891 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6893 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6894 strcat(bookMove, bookHit);
6895 HandleMachineMove(bookMove, &first);
6901 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6903 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6904 Markers *m = (Markers *) closure;
6905 if(rf == fromY && ff == fromX)
6906 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6907 || kind == WhiteCapturesEnPassant
6908 || kind == BlackCapturesEnPassant);
6909 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6913 MarkTargetSquares (int clear)
6916 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6917 !appData.testLegality || gameMode == EditPosition) return;
6919 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6922 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6923 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6924 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6926 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6929 DrawPosition(TRUE, NULL);
6933 Explode (Board board, int fromX, int fromY, int toX, int toY)
6935 if(gameInfo.variant == VariantAtomic &&
6936 (board[toY][toX] != EmptySquare || // capture?
6937 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6938 board[fromY][fromX] == BlackPawn )
6940 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6946 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6949 CanPromote (ChessSquare piece, int y)
6951 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6952 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6953 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6954 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6955 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6956 gameInfo.variant == VariantMakruk) return FALSE;
6957 return (piece == BlackPawn && y == 1 ||
6958 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6959 piece == BlackLance && y == 1 ||
6960 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6964 LeftClick (ClickType clickType, int xPix, int yPix)
6967 Boolean saveAnimate;
6968 static int second = 0, promotionChoice = 0, clearFlag = 0;
6969 char promoChoice = NULLCHAR;
6971 static TimeMark lastClickTime, prevClickTime;
6973 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6975 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6977 if (clickType == Press) ErrorPopDown();
6979 x = EventToSquare(xPix, BOARD_WIDTH);
6980 y = EventToSquare(yPix, BOARD_HEIGHT);
6981 if (!flipView && y >= 0) {
6982 y = BOARD_HEIGHT - 1 - y;
6984 if (flipView && x >= 0) {
6985 x = BOARD_WIDTH - 1 - x;
6988 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6989 defaultPromoChoice = promoSweep;
6990 promoSweep = EmptySquare; // terminate sweep
6991 promoDefaultAltered = TRUE;
6992 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6995 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6996 if(clickType == Release) return; // ignore upclick of click-click destination
6997 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6998 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6999 if(gameInfo.holdingsWidth &&
7000 (WhiteOnMove(currentMove)
7001 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7002 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7003 // click in right holdings, for determining promotion piece
7004 ChessSquare p = boards[currentMove][y][x];
7005 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7006 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7007 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7008 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7013 DrawPosition(FALSE, boards[currentMove]);
7017 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7018 if(clickType == Press
7019 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7020 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7021 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7024 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7025 // could be static click on premove from-square: abort premove
7027 ClearPremoveHighlights();
7030 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7031 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7033 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7034 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7035 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7036 defaultPromoChoice = DefaultPromoChoice(side);
7039 autoQueen = appData.alwaysPromoteToQueen;
7043 gatingPiece = EmptySquare;
7044 if (clickType != Press) {
7045 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7046 DragPieceEnd(xPix, yPix); dragging = 0;
7047 DrawPosition(FALSE, NULL);
7051 doubleClick = FALSE;
7052 fromX = x; fromY = y; toX = toY = -1;
7053 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7054 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7055 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7057 if (OKToStartUserMove(fromX, fromY)) {
7059 MarkTargetSquares(0);
7060 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7061 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7062 promoSweep = defaultPromoChoice;
7063 selectFlag = 0; lastX = xPix; lastY = yPix;
7064 Sweep(0); // Pawn that is going to promote: preview promotion piece
7065 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7067 if (appData.highlightDragging) {
7068 SetHighlights(fromX, fromY, -1, -1);
7070 } else fromX = fromY = -1;
7076 if (clickType == Press && gameMode != EditPosition) {
7081 // ignore off-board to clicks
7082 if(y < 0 || x < 0) return;
7084 /* Check if clicking again on the same color piece */
7085 fromP = boards[currentMove][fromY][fromX];
7086 toP = boards[currentMove][y][x];
7087 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7088 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7089 WhitePawn <= toP && toP <= WhiteKing &&
7090 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7091 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7092 (BlackPawn <= fromP && fromP <= BlackKing &&
7093 BlackPawn <= toP && toP <= BlackKing &&
7094 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7095 !(fromP == BlackKing && toP == BlackRook && frc))) {
7096 /* Clicked again on same color piece -- changed his mind */
7097 second = (x == fromX && y == fromY);
7098 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7099 second = FALSE; // first double-click rather than scond click
7100 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7102 promoDefaultAltered = FALSE;
7103 MarkTargetSquares(1);
7104 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7105 if (appData.highlightDragging) {
7106 SetHighlights(x, y, -1, -1);
7110 if (OKToStartUserMove(x, y)) {
7111 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7112 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7113 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7114 gatingPiece = boards[currentMove][fromY][fromX];
7115 else gatingPiece = doubleClick ? fromP : EmptySquare;
7117 fromY = y; dragging = 1;
7118 MarkTargetSquares(0);
7119 DragPieceBegin(xPix, yPix, FALSE);
7120 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7121 promoSweep = defaultPromoChoice;
7122 selectFlag = 0; lastX = xPix; lastY = yPix;
7123 Sweep(0); // Pawn that is going to promote: preview promotion piece
7127 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7130 // ignore clicks on holdings
7131 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7134 if (clickType == Release && x == fromX && y == fromY) {
7135 DragPieceEnd(xPix, yPix); dragging = 0;
7137 // a deferred attempt to click-click move an empty square on top of a piece
7138 boards[currentMove][y][x] = EmptySquare;
7140 DrawPosition(FALSE, boards[currentMove]);
7141 fromX = fromY = -1; clearFlag = 0;
7144 if (appData.animateDragging) {
7145 /* Undo animation damage if any */
7146 DrawPosition(FALSE, NULL);
7149 /* Second up/down in same square; just abort move */
7152 gatingPiece = EmptySquare;
7155 ClearPremoveHighlights();
7157 /* First upclick in same square; start click-click mode */
7158 SetHighlights(x, y, -1, -1);
7165 /* we now have a different from- and (possibly off-board) to-square */
7166 /* Completed move */
7169 saveAnimate = appData.animate;
7170 MarkTargetSquares(1);
7171 if (clickType == Press) {
7172 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7173 // must be Edit Position mode with empty-square selected
7174 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7175 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7178 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7179 ChessSquare piece = boards[currentMove][fromY][fromX];
7180 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7181 promoSweep = defaultPromoChoice;
7182 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7183 selectFlag = 0; lastX = xPix; lastY = yPix;
7184 Sweep(0); // Pawn that is going to promote: preview promotion piece
7185 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7186 DrawPosition(FALSE, boards[currentMove]);
7189 /* Finish clickclick move */
7190 if (appData.animate || appData.highlightLastMove) {
7191 SetHighlights(fromX, fromY, toX, toY);
7196 /* Finish drag move */
7197 if (appData.highlightLastMove) {
7198 SetHighlights(fromX, fromY, toX, toY);
7202 DragPieceEnd(xPix, yPix); dragging = 0;
7203 /* Don't animate move and drag both */
7204 appData.animate = FALSE;
7207 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7208 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7209 ChessSquare piece = boards[currentMove][fromY][fromX];
7210 if(gameMode == EditPosition && piece != EmptySquare &&
7211 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7214 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7215 n = PieceToNumber(piece - (int)BlackPawn);
7216 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7217 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7218 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7220 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7221 n = PieceToNumber(piece);
7222 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7223 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7224 boards[currentMove][n][BOARD_WIDTH-2]++;
7226 boards[currentMove][fromY][fromX] = EmptySquare;
7230 DrawPosition(TRUE, boards[currentMove]);
7234 // off-board moves should not be highlighted
7235 if(x < 0 || y < 0) ClearHighlights();
7237 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7239 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7240 SetHighlights(fromX, fromY, toX, toY);
7241 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7242 // [HGM] super: promotion to captured piece selected from holdings
7243 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7244 promotionChoice = TRUE;
7245 // kludge follows to temporarily execute move on display, without promoting yet
7246 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7247 boards[currentMove][toY][toX] = p;
7248 DrawPosition(FALSE, boards[currentMove]);
7249 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7250 boards[currentMove][toY][toX] = q;
7251 DisplayMessage("Click in holdings to choose piece", "");
7256 int oldMove = currentMove;
7257 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7258 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7259 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7260 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7261 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7262 DrawPosition(TRUE, boards[currentMove]);
7265 appData.animate = saveAnimate;
7266 if (appData.animate || appData.animateDragging) {
7267 /* Undo animation damage if needed */
7268 DrawPosition(FALSE, NULL);
7273 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7274 { // front-end-free part taken out of PieceMenuPopup
7275 int whichMenu; int xSqr, ySqr;
7277 if(seekGraphUp) { // [HGM] seekgraph
7278 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7279 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7283 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7284 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7285 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7286 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7287 if(action == Press) {
7288 originalFlip = flipView;
7289 flipView = !flipView; // temporarily flip board to see game from partners perspective
7290 DrawPosition(TRUE, partnerBoard);
7291 DisplayMessage(partnerStatus, "");
7293 } else if(action == Release) {
7294 flipView = originalFlip;
7295 DrawPosition(TRUE, boards[currentMove]);
7301 xSqr = EventToSquare(x, BOARD_WIDTH);
7302 ySqr = EventToSquare(y, BOARD_HEIGHT);
7303 if (action == Release) {
7304 if(pieceSweep != EmptySquare) {
7305 EditPositionMenuEvent(pieceSweep, toX, toY);
7306 pieceSweep = EmptySquare;
7307 } else UnLoadPV(); // [HGM] pv
7309 if (action != Press) return -2; // return code to be ignored
7312 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7314 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7315 if (xSqr < 0 || ySqr < 0) return -1;
7316 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7317 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7318 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7319 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7323 if(!appData.icsEngineAnalyze) return -1;
7324 case IcsPlayingWhite:
7325 case IcsPlayingBlack:
7326 if(!appData.zippyPlay) goto noZip;
7329 case MachinePlaysWhite:
7330 case MachinePlaysBlack:
7331 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7332 if (!appData.dropMenu) {
7334 return 2; // flag front-end to grab mouse events
7336 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7337 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7340 if (xSqr < 0 || ySqr < 0) return -1;
7341 if (!appData.dropMenu || appData.testLegality &&
7342 gameInfo.variant != VariantBughouse &&
7343 gameInfo.variant != VariantCrazyhouse) return -1;
7344 whichMenu = 1; // drop menu
7350 if (((*fromX = xSqr) < 0) ||
7351 ((*fromY = ySqr) < 0)) {
7352 *fromX = *fromY = -1;
7356 *fromX = BOARD_WIDTH - 1 - *fromX;
7358 *fromY = BOARD_HEIGHT - 1 - *fromY;
7364 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7366 // char * hint = lastHint;
7367 FrontEndProgramStats stats;
7369 stats.which = cps == &first ? 0 : 1;
7370 stats.depth = cpstats->depth;
7371 stats.nodes = cpstats->nodes;
7372 stats.score = cpstats->score;
7373 stats.time = cpstats->time;
7374 stats.pv = cpstats->movelist;
7375 stats.hint = lastHint;
7376 stats.an_move_index = 0;
7377 stats.an_move_count = 0;
7379 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7380 stats.hint = cpstats->move_name;
7381 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7382 stats.an_move_count = cpstats->nr_moves;
7385 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
7387 SetProgramStats( &stats );
7391 ClearEngineOutputPane (int which)
7393 static FrontEndProgramStats dummyStats;
7394 dummyStats.which = which;
7395 dummyStats.pv = "#";
7396 SetProgramStats( &dummyStats );
7399 #define MAXPLAYERS 500
7402 TourneyStandings (int display)
7404 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7405 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7406 char result, *p, *names[MAXPLAYERS];
7408 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7409 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7410 names[0] = p = strdup(appData.participants);
7411 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7413 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7415 while(result = appData.results[nr]) {
7416 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7417 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7418 wScore = bScore = 0;
7420 case '+': wScore = 2; break;
7421 case '-': bScore = 2; break;
7422 case '=': wScore = bScore = 1; break;
7424 case '*': return strdup("busy"); // tourney not finished
7432 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7433 for(w=0; w<nPlayers; w++) {
7435 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7436 ranking[w] = b; points[w] = bScore; score[b] = -2;
7438 p = malloc(nPlayers*34+1);
7439 for(w=0; w<nPlayers && w<display; w++)
7440 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7446 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7447 { // count all piece types
7449 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7450 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7451 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7454 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7455 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7456 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7457 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7458 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7459 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7464 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7466 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7467 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7469 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7470 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7471 if(myPawns == 2 && nMine == 3) // KPP
7472 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7473 if(myPawns == 1 && nMine == 2) // KP
7474 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7475 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7476 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7477 if(myPawns) return FALSE;
7478 if(pCnt[WhiteRook+side])
7479 return pCnt[BlackRook-side] ||
7480 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7481 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7482 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7483 if(pCnt[WhiteCannon+side]) {
7484 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7485 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7487 if(pCnt[WhiteKnight+side])
7488 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7493 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7495 VariantClass v = gameInfo.variant;
7497 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7498 if(v == VariantShatranj) return TRUE; // always winnable through baring
7499 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7500 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7502 if(v == VariantXiangqi) {
7503 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7505 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7506 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7507 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7508 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7509 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7510 if(stale) // we have at least one last-rank P plus perhaps C
7511 return majors // KPKX
7512 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7514 return pCnt[WhiteFerz+side] // KCAK
7515 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7516 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7517 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7519 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7520 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7522 if(nMine == 1) return FALSE; // bare King
7523 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
7524 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7525 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7526 // by now we have King + 1 piece (or multiple Bishops on the same color)
7527 if(pCnt[WhiteKnight+side])
7528 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7529 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7530 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7532 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7533 if(pCnt[WhiteAlfil+side])
7534 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7535 if(pCnt[WhiteWazir+side])
7536 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7543 CompareWithRights (Board b1, Board b2)
7546 if(!CompareBoards(b1, b2)) return FALSE;
7547 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7548 /* compare castling rights */
7549 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7550 rights++; /* King lost rights, while rook still had them */
7551 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7552 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7553 rights++; /* but at least one rook lost them */
7555 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7557 if( b1[CASTLING][5] != NoRights ) {
7558 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7565 Adjudicate (ChessProgramState *cps)
7566 { // [HGM] some adjudications useful with buggy engines
7567 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7568 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7569 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7570 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7571 int k, count = 0; static int bare = 1;
7572 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7573 Boolean canAdjudicate = !appData.icsActive;
7575 // most tests only when we understand the game, i.e. legality-checking on
7576 if( appData.testLegality )
7577 { /* [HGM] Some more adjudications for obstinate engines */
7578 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7579 static int moveCount = 6;
7581 char *reason = NULL;
7583 /* Count what is on board. */
7584 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7586 /* Some material-based adjudications that have to be made before stalemate test */
7587 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7588 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7589 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7590 if(canAdjudicate && appData.checkMates) {
7592 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7593 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7594 "Xboard adjudication: King destroyed", GE_XBOARD );
7599 /* Bare King in Shatranj (loses) or Losers (wins) */
7600 if( nrW == 1 || nrB == 1) {
7601 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7602 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7603 if(canAdjudicate && appData.checkMates) {
7605 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7606 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7607 "Xboard adjudication: Bare king", GE_XBOARD );
7611 if( gameInfo.variant == VariantShatranj && --bare < 0)
7613 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7614 if(canAdjudicate && appData.checkMates) {
7615 /* but only adjudicate if adjudication enabled */
7617 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7618 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7619 "Xboard adjudication: Bare king", GE_XBOARD );
7626 // don't wait for engine to announce game end if we can judge ourselves
7627 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7629 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7630 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7631 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7632 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7635 reason = "Xboard adjudication: 3rd check";
7636 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7646 reason = "Xboard adjudication: Stalemate";
7647 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7648 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7649 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7650 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7651 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7652 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7653 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7654 EP_CHECKMATE : EP_WINS);
7655 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7656 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7660 reason = "Xboard adjudication: Checkmate";
7661 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7665 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7667 result = GameIsDrawn; break;
7669 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7671 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7675 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7677 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678 GameEnds( result, reason, GE_XBOARD );
7682 /* Next absolutely insufficient mating material. */
7683 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7684 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7685 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7687 /* always flag draws, for judging claims */
7688 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7690 if(canAdjudicate && appData.materialDraws) {
7691 /* but only adjudicate them if adjudication enabled */
7692 if(engineOpponent) {
7693 SendToProgram("force\n", engineOpponent); // suppress reply
7694 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7696 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7701 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7702 if(gameInfo.variant == VariantXiangqi ?
7703 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7705 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7706 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7707 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7708 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7710 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7711 { /* if the first 3 moves do not show a tactical win, declare draw */
7712 if(engineOpponent) {
7713 SendToProgram("force\n", engineOpponent); // suppress reply
7714 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7716 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7719 } else moveCount = 6;
7722 // Repetition draws and 50-move rule can be applied independently of legality testing
7724 /* Check for rep-draws */
7726 for(k = forwardMostMove-2;
7727 k>=backwardMostMove && k>=forwardMostMove-100 &&
7728 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7729 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7732 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7733 /* compare castling rights */
7734 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7735 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7736 rights++; /* King lost rights, while rook still had them */
7737 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7738 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7739 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7740 rights++; /* but at least one rook lost them */
7742 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7743 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7745 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7746 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7747 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7750 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7751 && appData.drawRepeats > 1) {
7752 /* adjudicate after user-specified nr of repeats */
7753 int result = GameIsDrawn;
7754 char *details = "XBoard adjudication: repetition draw";
7755 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7756 // [HGM] xiangqi: check for forbidden perpetuals
7757 int m, ourPerpetual = 1, hisPerpetual = 1;
7758 for(m=forwardMostMove; m>k; m-=2) {
7759 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7760 ourPerpetual = 0; // the current mover did not always check
7761 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7762 hisPerpetual = 0; // the opponent did not always check
7764 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7765 ourPerpetual, hisPerpetual);
7766 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7767 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7768 details = "Xboard adjudication: perpetual checking";
7770 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7771 break; // (or we would have caught him before). Abort repetition-checking loop.
7773 // Now check for perpetual chases
7774 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7775 hisPerpetual = PerpetualChase(k, forwardMostMove);
7776 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7777 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7778 static char resdet[MSG_SIZ];
7779 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7781 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7783 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7784 break; // Abort repetition-checking loop.
7786 // if neither of us is checking or chasing all the time, or both are, it is draw
7788 if(engineOpponent) {
7789 SendToProgram("force\n", engineOpponent); // suppress reply
7790 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7792 GameEnds( result, details, GE_XBOARD );
7795 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7796 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7800 /* Now we test for 50-move draws. Determine ply count */
7801 count = forwardMostMove;
7802 /* look for last irreversble move */
7803 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7805 /* if we hit starting position, add initial plies */
7806 if( count == backwardMostMove )
7807 count -= initialRulePlies;
7808 count = forwardMostMove - count;
7809 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7810 // adjust reversible move counter for checks in Xiangqi
7811 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7812 if(i < backwardMostMove) i = backwardMostMove;
7813 while(i <= forwardMostMove) {
7814 lastCheck = inCheck; // check evasion does not count
7815 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7816 if(inCheck || lastCheck) count--; // check does not count
7821 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7822 /* this is used to judge if draw claims are legal */
7823 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7824 if(engineOpponent) {
7825 SendToProgram("force\n", engineOpponent); // suppress reply
7826 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7828 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7832 /* if draw offer is pending, treat it as a draw claim
7833 * when draw condition present, to allow engines a way to
7834 * claim draws before making their move to avoid a race
7835 * condition occurring after their move
7837 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7839 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7840 p = "Draw claim: 50-move rule";
7841 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7842 p = "Draw claim: 3-fold repetition";
7843 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7844 p = "Draw claim: insufficient mating material";
7845 if( p != NULL && canAdjudicate) {
7846 if(engineOpponent) {
7847 SendToProgram("force\n", engineOpponent); // suppress reply
7848 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7850 GameEnds( GameIsDrawn, p, GE_XBOARD );
7855 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7856 if(engineOpponent) {
7857 SendToProgram("force\n", engineOpponent); // suppress reply
7858 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7860 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7867 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7868 { // [HGM] book: this routine intercepts moves to simulate book replies
7869 char *bookHit = NULL;
7871 //first determine if the incoming move brings opponent into his book
7872 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7873 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7874 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7875 if(bookHit != NULL && !cps->bookSuspend) {
7876 // make sure opponent is not going to reply after receiving move to book position
7877 SendToProgram("force\n", cps);
7878 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7880 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7881 // now arrange restart after book miss
7883 // after a book hit we never send 'go', and the code after the call to this routine
7884 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7885 char buf[MSG_SIZ], *move = bookHit;
7887 int fromX, fromY, toX, toY;
7891 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7892 &fromX, &fromY, &toX, &toY, &promoChar)) {
7893 (void) CoordsToAlgebraic(boards[forwardMostMove],
7894 PosFlags(forwardMostMove),
7895 fromY, fromX, toY, toX, promoChar, move);
7897 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7901 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7902 SendToProgram(buf, cps);
7903 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7904 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7905 SendToProgram("go\n", cps);
7906 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7907 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7908 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7909 SendToProgram("go\n", cps);
7910 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7912 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7916 ChessProgramState *savedState;
7918 DeferredBookMove (void)
7920 if(savedState->lastPing != savedState->lastPong)
7921 ScheduleDelayedEvent(DeferredBookMove, 10);
7923 HandleMachineMove(savedMessage, savedState);
7926 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7929 HandleMachineMove (char *message, ChessProgramState *cps)
7931 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7932 char realname[MSG_SIZ];
7933 int fromX, fromY, toX, toY;
7940 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7941 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7942 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7943 DisplayError(_("Invalid pairing from pairing engine"), 0);
7946 pairingReceived = 1;
7948 return; // Skim the pairing messages here.
7953 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7955 * Kludge to ignore BEL characters
7957 while (*message == '\007') message++;
7960 * [HGM] engine debug message: ignore lines starting with '#' character
7962 if(cps->debug && *message == '#') return;
7965 * Look for book output
7967 if (cps == &first && bookRequested) {
7968 if (message[0] == '\t' || message[0] == ' ') {
7969 /* Part of the book output is here; append it */
7970 strcat(bookOutput, message);
7971 strcat(bookOutput, " \n");
7973 } else if (bookOutput[0] != NULLCHAR) {
7974 /* All of book output has arrived; display it */
7975 char *p = bookOutput;
7976 while (*p != NULLCHAR) {
7977 if (*p == '\t') *p = ' ';
7980 DisplayInformation(bookOutput);
7981 bookRequested = FALSE;
7982 /* Fall through to parse the current output */
7987 * Look for machine move.
7989 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7990 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7992 /* This method is only useful on engines that support ping */
7993 if (cps->lastPing != cps->lastPong) {
7994 if (gameMode == BeginningOfGame) {
7995 /* Extra move from before last new; ignore */
7996 if (appData.debugMode) {
7997 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8000 if (appData.debugMode) {
8001 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8002 cps->which, gameMode);
8005 SendToProgram("undo\n", cps);
8011 case BeginningOfGame:
8012 /* Extra move from before last reset; ignore */
8013 if (appData.debugMode) {
8014 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8021 /* Extra move after we tried to stop. The mode test is
8022 not a reliable way of detecting this problem, but it's
8023 the best we can do on engines that don't support ping.
8025 if (appData.debugMode) {
8026 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8027 cps->which, gameMode);
8029 SendToProgram("undo\n", cps);
8032 case MachinePlaysWhite:
8033 case IcsPlayingWhite:
8034 machineWhite = TRUE;
8037 case MachinePlaysBlack:
8038 case IcsPlayingBlack:
8039 machineWhite = FALSE;
8042 case TwoMachinesPlay:
8043 machineWhite = (cps->twoMachinesColor[0] == 'w');
8046 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8047 if (appData.debugMode) {
8049 "Ignoring move out of turn by %s, gameMode %d"
8050 ", forwardMost %d\n",
8051 cps->which, gameMode, forwardMostMove);
8056 if(cps->alphaRank) AlphaRank(machineMove, 4);
8057 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8058 &fromX, &fromY, &toX, &toY, &promoChar)) {
8059 /* Machine move could not be parsed; ignore it. */
8060 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8061 machineMove, _(cps->which));
8062 DisplayError(buf1, 0);
8063 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8064 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8065 if (gameMode == TwoMachinesPlay) {
8066 GameEnds(machineWhite ? BlackWins : WhiteWins,
8072 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8073 /* So we have to redo legality test with true e.p. status here, */
8074 /* to make sure an illegal e.p. capture does not slip through, */
8075 /* to cause a forfeit on a justified illegal-move complaint */
8076 /* of the opponent. */
8077 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8079 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8080 fromY, fromX, toY, toX, promoChar);
8081 if(moveType == IllegalMove) {
8082 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8083 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8084 GameEnds(machineWhite ? BlackWins : WhiteWins,
8087 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8088 /* [HGM] Kludge to handle engines that send FRC-style castling
8089 when they shouldn't (like TSCP-Gothic) */
8091 case WhiteASideCastleFR:
8092 case BlackASideCastleFR:
8094 currentMoveString[2]++;
8096 case WhiteHSideCastleFR:
8097 case BlackHSideCastleFR:
8099 currentMoveString[2]--;
8101 default: ; // nothing to do, but suppresses warning of pedantic compilers
8104 hintRequested = FALSE;
8105 lastHint[0] = NULLCHAR;
8106 bookRequested = FALSE;
8107 /* Program may be pondering now */
8108 cps->maybeThinking = TRUE;
8109 if (cps->sendTime == 2) cps->sendTime = 1;
8110 if (cps->offeredDraw) cps->offeredDraw--;
8112 /* [AS] Save move info*/
8113 pvInfoList[ forwardMostMove ].score = programStats.score;
8114 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8115 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8117 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8119 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8120 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8123 while( count < adjudicateLossPlies ) {
8124 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8127 score = -score; /* Flip score for winning side */
8130 if( score > adjudicateLossThreshold ) {
8137 if( count >= adjudicateLossPlies ) {
8138 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8140 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141 "Xboard adjudication",
8148 if(Adjudicate(cps)) {
8149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8150 return; // [HGM] adjudicate: for all automatic game ends
8154 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8156 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8157 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8159 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8161 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8163 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8164 char buf[3*MSG_SIZ];
8166 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8167 programStats.score / 100.,
8169 programStats.time / 100.,
8170 (unsigned int)programStats.nodes,
8171 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8172 programStats.movelist);
8174 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8179 /* [AS] Clear stats for next move */
8180 ClearProgramStats();
8181 thinkOutput[0] = NULLCHAR;
8182 hiddenThinkOutputState = 0;
8185 if (gameMode == TwoMachinesPlay) {
8186 /* [HGM] relaying draw offers moved to after reception of move */
8187 /* and interpreting offer as claim if it brings draw condition */
8188 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8189 SendToProgram("draw\n", cps->other);
8191 if (cps->other->sendTime) {
8192 SendTimeRemaining(cps->other,
8193 cps->other->twoMachinesColor[0] == 'w');
8195 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8196 if (firstMove && !bookHit) {
8198 if (cps->other->useColors) {
8199 SendToProgram(cps->other->twoMachinesColor, cps->other);
8201 SendToProgram("go\n", cps->other);
8203 cps->other->maybeThinking = TRUE;
8206 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8208 if (!pausing && appData.ringBellAfterMoves) {
8213 * Reenable menu items that were disabled while
8214 * machine was thinking
8216 if (gameMode != TwoMachinesPlay)
8217 SetUserThinkingEnables();
8219 // [HGM] book: after book hit opponent has received move and is now in force mode
8220 // force the book reply into it, and then fake that it outputted this move by jumping
8221 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8223 static char bookMove[MSG_SIZ]; // a bit generous?
8225 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8226 strcat(bookMove, bookHit);
8229 programStats.nodes = programStats.depth = programStats.time =
8230 programStats.score = programStats.got_only_move = 0;
8231 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8233 if(cps->lastPing != cps->lastPong) {
8234 savedMessage = message; // args for deferred call
8236 ScheduleDelayedEvent(DeferredBookMove, 10);
8245 /* Set special modes for chess engines. Later something general
8246 * could be added here; for now there is just one kludge feature,
8247 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8248 * when "xboard" is given as an interactive command.
8250 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8251 cps->useSigint = FALSE;
8252 cps->useSigterm = FALSE;
8254 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8255 ParseFeatures(message+8, cps);
8256 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8259 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8260 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8261 int dummy, s=6; char buf[MSG_SIZ];
8262 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8263 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8264 if(startedFromSetupPosition) return;
8265 ParseFEN(boards[0], &dummy, message+s);
8266 DrawPosition(TRUE, boards[0]);
8267 startedFromSetupPosition = TRUE;
8270 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8271 * want this, I was asked to put it in, and obliged.
8273 if (!strncmp(message, "setboard ", 9)) {
8274 Board initial_position;
8276 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8278 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8279 DisplayError(_("Bad FEN received from engine"), 0);
8283 CopyBoard(boards[0], initial_position);
8284 initialRulePlies = FENrulePlies;
8285 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8286 else gameMode = MachinePlaysBlack;
8287 DrawPosition(FALSE, boards[currentMove]);
8293 * Look for communication commands
8295 if (!strncmp(message, "telluser ", 9)) {
8296 if(message[9] == '\\' && message[10] == '\\')
8297 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8299 DisplayNote(message + 9);
8302 if (!strncmp(message, "tellusererror ", 14)) {
8304 if(message[14] == '\\' && message[15] == '\\')
8305 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8307 DisplayError(message + 14, 0);
8310 if (!strncmp(message, "tellopponent ", 13)) {
8311 if (appData.icsActive) {
8313 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8317 DisplayNote(message + 13);
8321 if (!strncmp(message, "tellothers ", 11)) {
8322 if (appData.icsActive) {
8324 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8330 if (!strncmp(message, "tellall ", 8)) {
8331 if (appData.icsActive) {
8333 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8337 DisplayNote(message + 8);
8341 if (strncmp(message, "warning", 7) == 0) {
8342 /* Undocumented feature, use tellusererror in new code */
8343 DisplayError(message, 0);
8346 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8347 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8348 strcat(realname, " query");
8349 AskQuestion(realname, buf2, buf1, cps->pr);
8352 /* Commands from the engine directly to ICS. We don't allow these to be
8353 * sent until we are logged on. Crafty kibitzes have been known to
8354 * interfere with the login process.
8357 if (!strncmp(message, "tellics ", 8)) {
8358 SendToICS(message + 8);
8362 if (!strncmp(message, "tellicsnoalias ", 15)) {
8363 SendToICS(ics_prefix);
8364 SendToICS(message + 15);
8368 /* The following are for backward compatibility only */
8369 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8370 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8371 SendToICS(ics_prefix);
8377 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8381 * If the move is illegal, cancel it and redraw the board.
8382 * Also deal with other error cases. Matching is rather loose
8383 * here to accommodate engines written before the spec.
8385 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8386 strncmp(message, "Error", 5) == 0) {
8387 if (StrStr(message, "name") ||
8388 StrStr(message, "rating") || StrStr(message, "?") ||
8389 StrStr(message, "result") || StrStr(message, "board") ||
8390 StrStr(message, "bk") || StrStr(message, "computer") ||
8391 StrStr(message, "variant") || StrStr(message, "hint") ||
8392 StrStr(message, "random") || StrStr(message, "depth") ||
8393 StrStr(message, "accepted")) {
8396 if (StrStr(message, "protover")) {
8397 /* Program is responding to input, so it's apparently done
8398 initializing, and this error message indicates it is
8399 protocol version 1. So we don't need to wait any longer
8400 for it to initialize and send feature commands. */
8401 FeatureDone(cps, 1);
8402 cps->protocolVersion = 1;
8405 cps->maybeThinking = FALSE;
8407 if (StrStr(message, "draw")) {
8408 /* Program doesn't have "draw" command */
8409 cps->sendDrawOffers = 0;
8412 if (cps->sendTime != 1 &&
8413 (StrStr(message, "time") || StrStr(message, "otim"))) {
8414 /* Program apparently doesn't have "time" or "otim" command */
8418 if (StrStr(message, "analyze")) {
8419 cps->analysisSupport = FALSE;
8420 cps->analyzing = FALSE;
8421 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8422 EditGameEvent(); // [HGM] try to preserve loaded game
8423 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8424 DisplayError(buf2, 0);
8427 if (StrStr(message, "(no matching move)st")) {
8428 /* Special kludge for GNU Chess 4 only */
8429 cps->stKludge = TRUE;
8430 SendTimeControl(cps, movesPerSession, timeControl,
8431 timeIncrement, appData.searchDepth,
8435 if (StrStr(message, "(no matching move)sd")) {
8436 /* Special kludge for GNU Chess 4 only */
8437 cps->sdKludge = TRUE;
8438 SendTimeControl(cps, movesPerSession, timeControl,
8439 timeIncrement, appData.searchDepth,
8443 if (!StrStr(message, "llegal")) {
8446 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8447 gameMode == IcsIdle) return;
8448 if (forwardMostMove <= backwardMostMove) return;
8449 if (pausing) PauseEvent();
8450 if(appData.forceIllegal) {
8451 // [HGM] illegal: machine refused move; force position after move into it
8452 SendToProgram("force\n", cps);
8453 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8454 // we have a real problem now, as SendBoard will use the a2a3 kludge
8455 // when black is to move, while there might be nothing on a2 or black
8456 // might already have the move. So send the board as if white has the move.
8457 // But first we must change the stm of the engine, as it refused the last move
8458 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8459 if(WhiteOnMove(forwardMostMove)) {
8460 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8461 SendBoard(cps, forwardMostMove); // kludgeless board
8463 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8464 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8465 SendBoard(cps, forwardMostMove+1); // kludgeless board
8467 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8468 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8469 gameMode == TwoMachinesPlay)
8470 SendToProgram("go\n", cps);
8473 if (gameMode == PlayFromGameFile) {
8474 /* Stop reading this game file */
8475 gameMode = EditGame;
8478 /* [HGM] illegal-move claim should forfeit game when Xboard */
8479 /* only passes fully legal moves */
8480 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8481 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8482 "False illegal-move claim", GE_XBOARD );
8483 return; // do not take back move we tested as valid
8485 currentMove = forwardMostMove-1;
8486 DisplayMove(currentMove-1); /* before DisplayMoveError */
8487 SwitchClocks(forwardMostMove-1); // [HGM] race
8488 DisplayBothClocks();
8489 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8490 parseList[currentMove], _(cps->which));
8491 DisplayMoveError(buf1);
8492 DrawPosition(FALSE, boards[currentMove]);
8494 SetUserThinkingEnables();
8497 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8498 /* Program has a broken "time" command that
8499 outputs a string not ending in newline.
8505 * If chess program startup fails, exit with an error message.
8506 * Attempts to recover here are futile. [HGM] Well, we try anyway
8508 if ((StrStr(message, "unknown host") != NULL)
8509 || (StrStr(message, "No remote directory") != NULL)
8510 || (StrStr(message, "not found") != NULL)
8511 || (StrStr(message, "No such file") != NULL)
8512 || (StrStr(message, "can't alloc") != NULL)
8513 || (StrStr(message, "Permission denied") != NULL)) {
8515 cps->maybeThinking = FALSE;
8516 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8517 _(cps->which), cps->program, cps->host, message);
8518 RemoveInputSource(cps->isr);
8519 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8521 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8524 appData.noChessProgram = TRUE;
8525 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8526 gameMode = BeginningOfGame; ModeHighlight();
8529 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8530 DisplayMessage("", ""); // erase waiting message
8531 DisplayError(buf1, 0);
8537 * Look for hint output
8539 if (sscanf(message, "Hint: %s", buf1) == 1) {
8540 if (cps == &first && hintRequested) {
8541 hintRequested = FALSE;
8542 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8543 &fromX, &fromY, &toX, &toY, &promoChar)) {
8544 (void) CoordsToAlgebraic(boards[forwardMostMove],
8545 PosFlags(forwardMostMove),
8546 fromY, fromX, toY, toX, promoChar, buf1);
8547 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8548 DisplayInformation(buf2);
8550 /* Hint move could not be parsed!? */
8551 snprintf(buf2, sizeof(buf2),
8552 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8553 buf1, _(cps->which));
8554 DisplayError(buf2, 0);
8557 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8563 * Ignore other messages if game is not in progress
8565 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8566 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8569 * look for win, lose, draw, or draw offer
8571 if (strncmp(message, "1-0", 3) == 0) {
8572 char *p, *q, *r = "";
8573 p = strchr(message, '{');
8581 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8583 } else if (strncmp(message, "0-1", 3) == 0) {
8584 char *p, *q, *r = "";
8585 p = strchr(message, '{');
8593 /* Kludge for Arasan 4.1 bug */
8594 if (strcmp(r, "Black resigns") == 0) {
8595 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8598 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8600 } else if (strncmp(message, "1/2", 3) == 0) {
8601 char *p, *q, *r = "";
8602 p = strchr(message, '{');
8611 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8614 } else if (strncmp(message, "White resign", 12) == 0) {
8615 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8617 } else if (strncmp(message, "Black resign", 12) == 0) {
8618 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8620 } else if (strncmp(message, "White matches", 13) == 0 ||
8621 strncmp(message, "Black matches", 13) == 0 ) {
8622 /* [HGM] ignore GNUShogi noises */
8624 } else if (strncmp(message, "White", 5) == 0 &&
8625 message[5] != '(' &&
8626 StrStr(message, "Black") == NULL) {
8627 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8629 } else if (strncmp(message, "Black", 5) == 0 &&
8630 message[5] != '(') {
8631 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8633 } else if (strcmp(message, "resign") == 0 ||
8634 strcmp(message, "computer resigns") == 0) {
8636 case MachinePlaysBlack:
8637 case IcsPlayingBlack:
8638 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8640 case MachinePlaysWhite:
8641 case IcsPlayingWhite:
8642 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8644 case TwoMachinesPlay:
8645 if (cps->twoMachinesColor[0] == 'w')
8646 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8648 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8655 } else if (strncmp(message, "opponent mates", 14) == 0) {
8657 case MachinePlaysBlack:
8658 case IcsPlayingBlack:
8659 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8661 case MachinePlaysWhite:
8662 case IcsPlayingWhite:
8663 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8665 case TwoMachinesPlay:
8666 if (cps->twoMachinesColor[0] == 'w')
8667 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8669 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8676 } else if (strncmp(message, "computer mates", 14) == 0) {
8678 case MachinePlaysBlack:
8679 case IcsPlayingBlack:
8680 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8682 case MachinePlaysWhite:
8683 case IcsPlayingWhite:
8684 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8686 case TwoMachinesPlay:
8687 if (cps->twoMachinesColor[0] == 'w')
8688 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8690 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8697 } else if (strncmp(message, "checkmate", 9) == 0) {
8698 if (WhiteOnMove(forwardMostMove)) {
8699 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8701 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8704 } else if (strstr(message, "Draw") != NULL ||
8705 strstr(message, "game is a draw") != NULL) {
8706 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8708 } else if (strstr(message, "offer") != NULL &&
8709 strstr(message, "draw") != NULL) {
8711 if (appData.zippyPlay && first.initDone) {
8712 /* Relay offer to ICS */
8713 SendToICS(ics_prefix);
8714 SendToICS("draw\n");
8717 cps->offeredDraw = 2; /* valid until this engine moves twice */
8718 if (gameMode == TwoMachinesPlay) {
8719 if (cps->other->offeredDraw) {
8720 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8721 /* [HGM] in two-machine mode we delay relaying draw offer */
8722 /* until after we also have move, to see if it is really claim */
8724 } else if (gameMode == MachinePlaysWhite ||
8725 gameMode == MachinePlaysBlack) {
8726 if (userOfferedDraw) {
8727 DisplayInformation(_("Machine accepts your draw offer"));
8728 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8730 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8737 * Look for thinking output
8739 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8740 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8742 int plylev, mvleft, mvtot, curscore, time;
8743 char mvname[MOVE_LEN];
8747 int prefixHint = FALSE;
8748 mvname[0] = NULLCHAR;
8751 case MachinePlaysBlack:
8752 case IcsPlayingBlack:
8753 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8755 case MachinePlaysWhite:
8756 case IcsPlayingWhite:
8757 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8762 case IcsObserving: /* [DM] icsEngineAnalyze */
8763 if (!appData.icsEngineAnalyze) ignore = TRUE;
8765 case TwoMachinesPlay:
8766 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8776 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8778 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8779 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8781 if (plyext != ' ' && plyext != '\t') {
8785 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8786 if( cps->scoreIsAbsolute &&
8787 ( gameMode == MachinePlaysBlack ||
8788 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8789 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8790 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8791 !WhiteOnMove(currentMove)
8794 curscore = -curscore;
8797 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8799 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8802 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8803 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8804 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8805 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8806 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8807 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8809 } else DisplayError(_("failed writing PV"), 0);
8812 tempStats.depth = plylev;
8813 tempStats.nodes = nodes;
8814 tempStats.time = time;
8815 tempStats.score = curscore;
8816 tempStats.got_only_move = 0;
8818 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8821 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8822 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8823 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8824 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8825 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8826 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8827 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8828 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8831 /* Buffer overflow protection */
8832 if (pv[0] != NULLCHAR) {
8833 if (strlen(pv) >= sizeof(tempStats.movelist)
8834 && appData.debugMode) {
8836 "PV is too long; using the first %u bytes.\n",
8837 (unsigned) sizeof(tempStats.movelist) - 1);
8840 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8842 sprintf(tempStats.movelist, " no PV\n");
8845 if (tempStats.seen_stat) {
8846 tempStats.ok_to_send = 1;
8849 if (strchr(tempStats.movelist, '(') != NULL) {
8850 tempStats.line_is_book = 1;
8851 tempStats.nr_moves = 0;
8852 tempStats.moves_left = 0;
8854 tempStats.line_is_book = 0;
8857 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8858 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8860 SendProgramStatsToFrontend( cps, &tempStats );
8863 [AS] Protect the thinkOutput buffer from overflow... this
8864 is only useful if buf1 hasn't overflowed first!
8866 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8868 (gameMode == TwoMachinesPlay ?
8869 ToUpper(cps->twoMachinesColor[0]) : ' '),
8870 ((double) curscore) / 100.0,
8871 prefixHint ? lastHint : "",
8872 prefixHint ? " " : "" );
8874 if( buf1[0] != NULLCHAR ) {
8875 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8877 if( strlen(pv) > max_len ) {
8878 if( appData.debugMode) {
8879 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8881 pv[max_len+1] = '\0';
8884 strcat( thinkOutput, pv);
8887 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8888 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8889 DisplayMove(currentMove - 1);
8893 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8894 /* crafty (9.25+) says "(only move) <move>"
8895 * if there is only 1 legal move
8897 sscanf(p, "(only move) %s", buf1);
8898 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8899 sprintf(programStats.movelist, "%s (only move)", buf1);
8900 programStats.depth = 1;
8901 programStats.nr_moves = 1;
8902 programStats.moves_left = 1;
8903 programStats.nodes = 1;
8904 programStats.time = 1;
8905 programStats.got_only_move = 1;
8907 /* Not really, but we also use this member to
8908 mean "line isn't going to change" (Crafty
8909 isn't searching, so stats won't change) */
8910 programStats.line_is_book = 1;
8912 SendProgramStatsToFrontend( cps, &programStats );
8914 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8915 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8916 DisplayMove(currentMove - 1);
8919 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8920 &time, &nodes, &plylev, &mvleft,
8921 &mvtot, mvname) >= 5) {
8922 /* The stat01: line is from Crafty (9.29+) in response
8923 to the "." command */
8924 programStats.seen_stat = 1;
8925 cps->maybeThinking = TRUE;
8927 if (programStats.got_only_move || !appData.periodicUpdates)
8930 programStats.depth = plylev;
8931 programStats.time = time;
8932 programStats.nodes = nodes;
8933 programStats.moves_left = mvleft;
8934 programStats.nr_moves = mvtot;
8935 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8936 programStats.ok_to_send = 1;
8937 programStats.movelist[0] = '\0';
8939 SendProgramStatsToFrontend( cps, &programStats );
8943 } else if (strncmp(message,"++",2) == 0) {
8944 /* Crafty 9.29+ outputs this */
8945 programStats.got_fail = 2;
8948 } else if (strncmp(message,"--",2) == 0) {
8949 /* Crafty 9.29+ outputs this */
8950 programStats.got_fail = 1;
8953 } else if (thinkOutput[0] != NULLCHAR &&
8954 strncmp(message, " ", 4) == 0) {
8955 unsigned message_len;
8958 while (*p && *p == ' ') p++;
8960 message_len = strlen( p );
8962 /* [AS] Avoid buffer overflow */
8963 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8964 strcat(thinkOutput, " ");
8965 strcat(thinkOutput, p);
8968 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8969 strcat(programStats.movelist, " ");
8970 strcat(programStats.movelist, p);
8973 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8974 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8975 DisplayMove(currentMove - 1);
8983 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8984 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8986 ChessProgramStats cpstats;
8988 if (plyext != ' ' && plyext != '\t') {
8992 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8993 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8994 curscore = -curscore;
8997 cpstats.depth = plylev;
8998 cpstats.nodes = nodes;
8999 cpstats.time = time;
9000 cpstats.score = curscore;
9001 cpstats.got_only_move = 0;
9002 cpstats.movelist[0] = '\0';
9004 if (buf1[0] != NULLCHAR) {
9005 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9008 cpstats.ok_to_send = 0;
9009 cpstats.line_is_book = 0;
9010 cpstats.nr_moves = 0;
9011 cpstats.moves_left = 0;
9013 SendProgramStatsToFrontend( cps, &cpstats );
9020 /* Parse a game score from the character string "game", and
9021 record it as the history of the current game. The game
9022 score is NOT assumed to start from the standard position.
9023 The display is not updated in any way.
9026 ParseGameHistory (char *game)
9029 int fromX, fromY, toX, toY, boardIndex;
9034 if (appData.debugMode)
9035 fprintf(debugFP, "Parsing game history: %s\n", game);
9037 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9038 gameInfo.site = StrSave(appData.icsHost);
9039 gameInfo.date = PGNDate();
9040 gameInfo.round = StrSave("-");
9042 /* Parse out names of players */
9043 while (*game == ' ') game++;
9045 while (*game != ' ') *p++ = *game++;
9047 gameInfo.white = StrSave(buf);
9048 while (*game == ' ') game++;
9050 while (*game != ' ' && *game != '\n') *p++ = *game++;
9052 gameInfo.black = StrSave(buf);
9055 boardIndex = blackPlaysFirst ? 1 : 0;
9058 yyboardindex = boardIndex;
9059 moveType = (ChessMove) Myylex();
9061 case IllegalMove: /* maybe suicide chess, etc. */
9062 if (appData.debugMode) {
9063 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9064 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9065 setbuf(debugFP, NULL);
9067 case WhitePromotion:
9068 case BlackPromotion:
9069 case WhiteNonPromotion:
9070 case BlackNonPromotion:
9072 case WhiteCapturesEnPassant:
9073 case BlackCapturesEnPassant:
9074 case WhiteKingSideCastle:
9075 case WhiteQueenSideCastle:
9076 case BlackKingSideCastle:
9077 case BlackQueenSideCastle:
9078 case WhiteKingSideCastleWild:
9079 case WhiteQueenSideCastleWild:
9080 case BlackKingSideCastleWild:
9081 case BlackQueenSideCastleWild:
9083 case WhiteHSideCastleFR:
9084 case WhiteASideCastleFR:
9085 case BlackHSideCastleFR:
9086 case BlackASideCastleFR:
9088 fromX = currentMoveString[0] - AAA;
9089 fromY = currentMoveString[1] - ONE;
9090 toX = currentMoveString[2] - AAA;
9091 toY = currentMoveString[3] - ONE;
9092 promoChar = currentMoveString[4];
9096 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9097 fromX = moveType == WhiteDrop ?
9098 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9099 (int) CharToPiece(ToLower(currentMoveString[0]));
9101 toX = currentMoveString[2] - AAA;
9102 toY = currentMoveString[3] - ONE;
9103 promoChar = NULLCHAR;
9107 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9108 if (appData.debugMode) {
9109 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9110 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9111 setbuf(debugFP, NULL);
9113 DisplayError(buf, 0);
9115 case ImpossibleMove:
9117 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9118 if (appData.debugMode) {
9119 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9120 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9121 setbuf(debugFP, NULL);
9123 DisplayError(buf, 0);
9126 if (boardIndex < backwardMostMove) {
9127 /* Oops, gap. How did that happen? */
9128 DisplayError(_("Gap in move list"), 0);
9131 backwardMostMove = blackPlaysFirst ? 1 : 0;
9132 if (boardIndex > forwardMostMove) {
9133 forwardMostMove = boardIndex;
9137 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9138 strcat(parseList[boardIndex-1], " ");
9139 strcat(parseList[boardIndex-1], yy_text);
9151 case GameUnfinished:
9152 if (gameMode == IcsExamining) {
9153 if (boardIndex < backwardMostMove) {
9154 /* Oops, gap. How did that happen? */
9157 backwardMostMove = blackPlaysFirst ? 1 : 0;
9160 gameInfo.result = moveType;
9161 p = strchr(yy_text, '{');
9162 if (p == NULL) p = strchr(yy_text, '(');
9165 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9167 q = strchr(p, *p == '{' ? '}' : ')');
9168 if (q != NULL) *q = NULLCHAR;
9171 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9172 gameInfo.resultDetails = StrSave(p);
9175 if (boardIndex >= forwardMostMove &&
9176 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9177 backwardMostMove = blackPlaysFirst ? 1 : 0;
9180 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9181 fromY, fromX, toY, toX, promoChar,
9182 parseList[boardIndex]);
9183 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9184 /* currentMoveString is set as a side-effect of yylex */
9185 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9186 strcat(moveList[boardIndex], "\n");
9188 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9189 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9195 if(gameInfo.variant != VariantShogi)
9196 strcat(parseList[boardIndex - 1], "+");
9200 strcat(parseList[boardIndex - 1], "#");
9207 /* Apply a move to the given board */
9209 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9211 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9212 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9214 /* [HGM] compute & store e.p. status and castling rights for new position */
9215 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9217 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9218 oldEP = (signed char)board[EP_STATUS];
9219 board[EP_STATUS] = EP_NONE;
9221 if (fromY == DROP_RANK) {
9223 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9224 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9227 piece = board[toY][toX] = (ChessSquare) fromX;
9231 if( board[toY][toX] != EmptySquare )
9232 board[EP_STATUS] = EP_CAPTURE;
9234 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9235 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9236 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9238 if( board[fromY][fromX] == WhitePawn ) {
9239 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9240 board[EP_STATUS] = EP_PAWN_MOVE;
9242 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9243 gameInfo.variant != VariantBerolina || toX < fromX)
9244 board[EP_STATUS] = toX | berolina;
9245 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9246 gameInfo.variant != VariantBerolina || toX > fromX)
9247 board[EP_STATUS] = toX;
9250 if( board[fromY][fromX] == BlackPawn ) {
9251 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9252 board[EP_STATUS] = EP_PAWN_MOVE;
9253 if( toY-fromY== -2) {
9254 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9255 gameInfo.variant != VariantBerolina || toX < fromX)
9256 board[EP_STATUS] = toX | berolina;
9257 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9258 gameInfo.variant != VariantBerolina || toX > fromX)
9259 board[EP_STATUS] = toX;
9263 for(i=0; i<nrCastlingRights; i++) {
9264 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9265 board[CASTLING][i] == toX && castlingRank[i] == toY
9266 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9269 if (fromX == toX && fromY == toY) return;
9271 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9272 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9273 if(gameInfo.variant == VariantKnightmate)
9274 king += (int) WhiteUnicorn - (int) WhiteKing;
9276 /* Code added by Tord: */
9277 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9278 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9279 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9280 board[fromY][fromX] = EmptySquare;
9281 board[toY][toX] = EmptySquare;
9282 if((toX > fromX) != (piece == WhiteRook)) {
9283 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9285 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9287 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9288 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9289 board[fromY][fromX] = EmptySquare;
9290 board[toY][toX] = EmptySquare;
9291 if((toX > fromX) != (piece == BlackRook)) {
9292 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9294 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9296 /* End of code added by Tord */
9298 } else if (board[fromY][fromX] == king
9299 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9300 && toY == fromY && toX > fromX+1) {
9301 board[fromY][fromX] = EmptySquare;
9302 board[toY][toX] = king;
9303 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9304 board[fromY][BOARD_RGHT-1] = EmptySquare;
9305 } else if (board[fromY][fromX] == king
9306 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9307 && toY == fromY && toX < fromX-1) {
9308 board[fromY][fromX] = EmptySquare;
9309 board[toY][toX] = king;
9310 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9311 board[fromY][BOARD_LEFT] = EmptySquare;
9312 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9313 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9314 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9316 /* white pawn promotion */
9317 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9318 if(gameInfo.variant==VariantBughouse ||
9319 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9320 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9321 board[fromY][fromX] = EmptySquare;
9322 } else if ((fromY >= BOARD_HEIGHT>>1)
9324 && gameInfo.variant != VariantXiangqi
9325 && gameInfo.variant != VariantBerolina
9326 && (board[fromY][fromX] == WhitePawn)
9327 && (board[toY][toX] == EmptySquare)) {
9328 board[fromY][fromX] = EmptySquare;
9329 board[toY][toX] = WhitePawn;
9330 captured = board[toY - 1][toX];
9331 board[toY - 1][toX] = EmptySquare;
9332 } else if ((fromY == BOARD_HEIGHT-4)
9334 && gameInfo.variant == VariantBerolina
9335 && (board[fromY][fromX] == WhitePawn)
9336 && (board[toY][toX] == EmptySquare)) {
9337 board[fromY][fromX] = EmptySquare;
9338 board[toY][toX] = WhitePawn;
9339 if(oldEP & EP_BEROLIN_A) {
9340 captured = board[fromY][fromX-1];
9341 board[fromY][fromX-1] = EmptySquare;
9342 }else{ captured = board[fromY][fromX+1];
9343 board[fromY][fromX+1] = EmptySquare;
9345 } else if (board[fromY][fromX] == king
9346 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9347 && toY == fromY && toX > fromX+1) {
9348 board[fromY][fromX] = EmptySquare;
9349 board[toY][toX] = king;
9350 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9351 board[fromY][BOARD_RGHT-1] = EmptySquare;
9352 } else if (board[fromY][fromX] == king
9353 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9354 && toY == fromY && toX < fromX-1) {
9355 board[fromY][fromX] = EmptySquare;
9356 board[toY][toX] = king;
9357 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9358 board[fromY][BOARD_LEFT] = EmptySquare;
9359 } else if (fromY == 7 && fromX == 3
9360 && board[fromY][fromX] == BlackKing
9361 && toY == 7 && toX == 5) {
9362 board[fromY][fromX] = EmptySquare;
9363 board[toY][toX] = BlackKing;
9364 board[fromY][7] = EmptySquare;
9365 board[toY][4] = BlackRook;
9366 } else if (fromY == 7 && fromX == 3
9367 && board[fromY][fromX] == BlackKing
9368 && toY == 7 && toX == 1) {
9369 board[fromY][fromX] = EmptySquare;
9370 board[toY][toX] = BlackKing;
9371 board[fromY][0] = EmptySquare;
9372 board[toY][2] = BlackRook;
9373 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9374 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375 && toY < promoRank && promoChar
9377 /* black pawn promotion */
9378 board[toY][toX] = CharToPiece(ToLower(promoChar));
9379 if(gameInfo.variant==VariantBughouse ||
9380 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9381 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9382 board[fromY][fromX] = EmptySquare;
9383 } else if ((fromY < BOARD_HEIGHT>>1)
9385 && gameInfo.variant != VariantXiangqi
9386 && gameInfo.variant != VariantBerolina
9387 && (board[fromY][fromX] == BlackPawn)
9388 && (board[toY][toX] == EmptySquare)) {
9389 board[fromY][fromX] = EmptySquare;
9390 board[toY][toX] = BlackPawn;
9391 captured = board[toY + 1][toX];
9392 board[toY + 1][toX] = EmptySquare;
9393 } else if ((fromY == 3)
9395 && gameInfo.variant == VariantBerolina
9396 && (board[fromY][fromX] == BlackPawn)
9397 && (board[toY][toX] == EmptySquare)) {
9398 board[fromY][fromX] = EmptySquare;
9399 board[toY][toX] = BlackPawn;
9400 if(oldEP & EP_BEROLIN_A) {
9401 captured = board[fromY][fromX-1];
9402 board[fromY][fromX-1] = EmptySquare;
9403 }else{ captured = board[fromY][fromX+1];
9404 board[fromY][fromX+1] = EmptySquare;
9407 board[toY][toX] = board[fromY][fromX];
9408 board[fromY][fromX] = EmptySquare;
9412 if (gameInfo.holdingsWidth != 0) {
9414 /* !!A lot more code needs to be written to support holdings */
9415 /* [HGM] OK, so I have written it. Holdings are stored in the */
9416 /* penultimate board files, so they are automaticlly stored */
9417 /* in the game history. */
9418 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9419 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9420 /* Delete from holdings, by decreasing count */
9421 /* and erasing image if necessary */
9422 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9423 if(p < (int) BlackPawn) { /* white drop */
9424 p -= (int)WhitePawn;
9425 p = PieceToNumber((ChessSquare)p);
9426 if(p >= gameInfo.holdingsSize) p = 0;
9427 if(--board[p][BOARD_WIDTH-2] <= 0)
9428 board[p][BOARD_WIDTH-1] = EmptySquare;
9429 if((int)board[p][BOARD_WIDTH-2] < 0)
9430 board[p][BOARD_WIDTH-2] = 0;
9431 } else { /* black drop */
9432 p -= (int)BlackPawn;
9433 p = PieceToNumber((ChessSquare)p);
9434 if(p >= gameInfo.holdingsSize) p = 0;
9435 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9436 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9437 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9438 board[BOARD_HEIGHT-1-p][1] = 0;
9441 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9442 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9443 /* [HGM] holdings: Add to holdings, if holdings exist */
9444 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9445 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9446 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9449 if (p >= (int) BlackPawn) {
9450 p -= (int)BlackPawn;
9451 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9452 /* in Shogi restore piece to its original first */
9453 captured = (ChessSquare) (DEMOTED captured);
9456 p = PieceToNumber((ChessSquare)p);
9457 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9458 board[p][BOARD_WIDTH-2]++;
9459 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9461 p -= (int)WhitePawn;
9462 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9463 captured = (ChessSquare) (DEMOTED captured);
9466 p = PieceToNumber((ChessSquare)p);
9467 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9468 board[BOARD_HEIGHT-1-p][1]++;
9469 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9472 } else if (gameInfo.variant == VariantAtomic) {
9473 if (captured != EmptySquare) {
9475 for (y = toY-1; y <= toY+1; y++) {
9476 for (x = toX-1; x <= toX+1; x++) {
9477 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9478 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9479 board[y][x] = EmptySquare;
9483 board[toY][toX] = EmptySquare;
9486 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9487 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9489 if(promoChar == '+') {
9490 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9491 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9492 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9493 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9494 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9495 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9496 board[toY][toX] = newPiece;
9498 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9499 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9500 // [HGM] superchess: take promotion piece out of holdings
9501 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9502 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9503 if(!--board[k][BOARD_WIDTH-2])
9504 board[k][BOARD_WIDTH-1] = EmptySquare;
9506 if(!--board[BOARD_HEIGHT-1-k][1])
9507 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9513 /* Updates forwardMostMove */
9515 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9517 // forwardMostMove++; // [HGM] bare: moved downstream
9519 (void) CoordsToAlgebraic(boards[forwardMostMove],
9520 PosFlags(forwardMostMove),
9521 fromY, fromX, toY, toX, promoChar,
9522 parseList[forwardMostMove]);
9524 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9525 int timeLeft; static int lastLoadFlag=0; int king, piece;
9526 piece = boards[forwardMostMove][fromY][fromX];
9527 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9528 if(gameInfo.variant == VariantKnightmate)
9529 king += (int) WhiteUnicorn - (int) WhiteKing;
9530 if(forwardMostMove == 0) {
9531 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9532 fprintf(serverMoves, "%s;", UserName());
9533 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9534 fprintf(serverMoves, "%s;", second.tidy);
9535 fprintf(serverMoves, "%s;", first.tidy);
9536 if(gameMode == MachinePlaysWhite)
9537 fprintf(serverMoves, "%s;", UserName());
9538 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9539 fprintf(serverMoves, "%s;", second.tidy);
9540 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9541 lastLoadFlag = loadFlag;
9543 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9544 // print castling suffix
9545 if( toY == fromY && piece == king ) {
9547 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9549 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9552 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9553 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9554 boards[forwardMostMove][toY][toX] == EmptySquare
9555 && fromX != toX && fromY != toY)
9556 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9558 if(promoChar != NULLCHAR)
9559 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9561 char buf[MOVE_LEN*2], *p; int len;
9562 fprintf(serverMoves, "/%d/%d",
9563 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9564 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9565 else timeLeft = blackTimeRemaining/1000;
9566 fprintf(serverMoves, "/%d", timeLeft);
9567 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9568 if(p = strchr(buf, '=')) *p = NULLCHAR;
9569 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9570 fprintf(serverMoves, "/%s", buf);
9572 fflush(serverMoves);
9575 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9576 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9579 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9580 if (commentList[forwardMostMove+1] != NULL) {
9581 free(commentList[forwardMostMove+1]);
9582 commentList[forwardMostMove+1] = NULL;
9584 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9585 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9586 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9587 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9588 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9589 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9590 adjustedClock = FALSE;
9591 gameInfo.result = GameUnfinished;
9592 if (gameInfo.resultDetails != NULL) {
9593 free(gameInfo.resultDetails);
9594 gameInfo.resultDetails = NULL;
9596 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9597 moveList[forwardMostMove - 1]);
9598 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9604 if(gameInfo.variant != VariantShogi)
9605 strcat(parseList[forwardMostMove - 1], "+");
9609 strcat(parseList[forwardMostMove - 1], "#");
9615 /* Updates currentMove if not pausing */
9617 ShowMove (int fromX, int fromY, int toX, int toY)
9619 int instant = (gameMode == PlayFromGameFile) ?
9620 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9621 if(appData.noGUI) return;
9622 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9624 if (forwardMostMove == currentMove + 1) {
9625 AnimateMove(boards[forwardMostMove - 1],
9626 fromX, fromY, toX, toY);
9628 if (appData.highlightLastMove) {
9629 SetHighlights(fromX, fromY, toX, toY);
9632 currentMove = forwardMostMove;
9635 if (instant) return;
9637 DisplayMove(currentMove - 1);
9638 DrawPosition(FALSE, boards[currentMove]);
9639 DisplayBothClocks();
9640 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9644 SendEgtPath (ChessProgramState *cps)
9645 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9646 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9648 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9651 char c, *q = name+1, *r, *s;
9653 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9654 while(*p && *p != ',') *q++ = *p++;
9656 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9657 strcmp(name, ",nalimov:") == 0 ) {
9658 // take nalimov path from the menu-changeable option first, if it is defined
9659 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9660 SendToProgram(buf,cps); // send egtbpath command for nalimov
9662 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9663 (s = StrStr(appData.egtFormats, name)) != NULL) {
9664 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9665 s = r = StrStr(s, ":") + 1; // beginning of path info
9666 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9667 c = *r; *r = 0; // temporarily null-terminate path info
9668 *--q = 0; // strip of trailig ':' from name
9669 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9671 SendToProgram(buf,cps); // send egtbpath command for this format
9673 if(*p == ',') p++; // read away comma to position for next format name
9678 InitChessProgram (ChessProgramState *cps, int setup)
9679 /* setup needed to setup FRC opening position */
9681 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9682 if (appData.noChessProgram) return;
9683 hintRequested = FALSE;
9684 bookRequested = FALSE;
9686 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9687 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9688 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9689 if(cps->memSize) { /* [HGM] memory */
9690 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9691 SendToProgram(buf, cps);
9693 SendEgtPath(cps); /* [HGM] EGT */
9694 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9695 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9696 SendToProgram(buf, cps);
9699 SendToProgram(cps->initString, cps);
9700 if (gameInfo.variant != VariantNormal &&
9701 gameInfo.variant != VariantLoadable
9702 /* [HGM] also send variant if board size non-standard */
9703 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9705 char *v = VariantName(gameInfo.variant);
9706 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9707 /* [HGM] in protocol 1 we have to assume all variants valid */
9708 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9709 DisplayFatalError(buf, 0, 1);
9713 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9714 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9715 if( gameInfo.variant == VariantXiangqi )
9716 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9717 if( gameInfo.variant == VariantShogi )
9718 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9719 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9720 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9721 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9722 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9723 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9724 if( gameInfo.variant == VariantCourier )
9725 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9726 if( gameInfo.variant == VariantSuper )
9727 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9728 if( gameInfo.variant == VariantGreat )
9729 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9730 if( gameInfo.variant == VariantSChess )
9731 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9732 if( gameInfo.variant == VariantGrand )
9733 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9736 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9737 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9738 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9739 if(StrStr(cps->variants, b) == NULL) {
9740 // specific sized variant not known, check if general sizing allowed
9741 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9742 if(StrStr(cps->variants, "boardsize") == NULL) {
9743 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9744 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9745 DisplayFatalError(buf, 0, 1);
9748 /* [HGM] here we really should compare with the maximum supported board size */
9751 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9752 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9753 SendToProgram(buf, cps);
9755 currentlyInitializedVariant = gameInfo.variant;
9757 /* [HGM] send opening position in FRC to first engine */
9759 SendToProgram("force\n", cps);
9761 /* engine is now in force mode! Set flag to wake it up after first move. */
9762 setboardSpoiledMachineBlack = 1;
9766 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9767 SendToProgram(buf, cps);
9769 cps->maybeThinking = FALSE;
9770 cps->offeredDraw = 0;
9771 if (!appData.icsActive) {
9772 SendTimeControl(cps, movesPerSession, timeControl,
9773 timeIncrement, appData.searchDepth,
9776 if (appData.showThinking
9777 // [HGM] thinking: four options require thinking output to be sent
9778 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9780 SendToProgram("post\n", cps);
9782 SendToProgram("hard\n", cps);
9783 if (!appData.ponderNextMove) {
9784 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9785 it without being sure what state we are in first. "hard"
9786 is not a toggle, so that one is OK.
9788 SendToProgram("easy\n", cps);
9791 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9792 SendToProgram(buf, cps);
9794 cps->initDone = TRUE;
9795 ClearEngineOutputPane(cps == &second);
9800 StartChessProgram (ChessProgramState *cps)
9805 if (appData.noChessProgram) return;
9806 cps->initDone = FALSE;
9808 if (strcmp(cps->host, "localhost") == 0) {
9809 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9810 } else if (*appData.remoteShell == NULLCHAR) {
9811 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9813 if (*appData.remoteUser == NULLCHAR) {
9814 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9817 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9818 cps->host, appData.remoteUser, cps->program);
9820 err = StartChildProcess(buf, "", &cps->pr);
9824 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9825 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9826 if(cps != &first) return;
9827 appData.noChessProgram = TRUE;
9830 // DisplayFatalError(buf, err, 1);
9831 // cps->pr = NoProc;
9836 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9837 if (cps->protocolVersion > 1) {
9838 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9839 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9840 cps->comboCnt = 0; // and values of combo boxes
9841 SendToProgram(buf, cps);
9843 SendToProgram("xboard\n", cps);
9848 TwoMachinesEventIfReady P((void))
9850 static int curMess = 0;
9851 if (first.lastPing != first.lastPong) {
9852 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9853 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9856 if (second.lastPing != second.lastPong) {
9857 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9858 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9861 DisplayMessage("", ""); curMess = 0;
9867 MakeName (char *template)
9871 static char buf[MSG_SIZ];
9875 clock = time((time_t *)NULL);
9876 tm = localtime(&clock);
9878 while(*p++ = *template++) if(p[-1] == '%') {
9879 switch(*template++) {
9880 case 0: *p = 0; return buf;
9881 case 'Y': i = tm->tm_year+1900; break;
9882 case 'y': i = tm->tm_year-100; break;
9883 case 'M': i = tm->tm_mon+1; break;
9884 case 'd': i = tm->tm_mday; break;
9885 case 'h': i = tm->tm_hour; break;
9886 case 'm': i = tm->tm_min; break;
9887 case 's': i = tm->tm_sec; break;
9890 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9896 CountPlayers (char *p)
9899 while(p = strchr(p, '\n')) p++, n++; // count participants
9904 WriteTourneyFile (char *results, FILE *f)
9905 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9906 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9907 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9908 // create a file with tournament description
9909 fprintf(f, "-participants {%s}\n", appData.participants);
9910 fprintf(f, "-seedBase %d\n", appData.seedBase);
9911 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9912 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9913 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9914 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9915 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9916 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9917 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9918 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9919 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9920 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9921 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9922 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9924 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9926 fprintf(f, "-mps %d\n", appData.movesPerSession);
9927 fprintf(f, "-tc %s\n", appData.timeControl);
9928 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9930 fprintf(f, "-results \"%s\"\n", results);
9935 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9938 Substitute (char *participants, int expunge)
9940 int i, changed, changes=0, nPlayers=0;
9941 char *p, *q, *r, buf[MSG_SIZ];
9942 if(participants == NULL) return;
9943 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9944 r = p = participants; q = appData.participants;
9945 while(*p && *p == *q) {
9946 if(*p == '\n') r = p+1, nPlayers++;
9949 if(*p) { // difference
9950 while(*p && *p++ != '\n');
9951 while(*q && *q++ != '\n');
9953 changes = 1 + (strcmp(p, q) != 0);
9955 if(changes == 1) { // a single engine mnemonic was changed
9956 q = r; while(*q) nPlayers += (*q++ == '\n');
9957 p = buf; while(*r && (*p = *r++) != '\n') p++;
9959 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9960 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9961 if(mnemonic[i]) { // The substitute is valid
9963 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9964 flock(fileno(f), LOCK_EX);
9965 ParseArgsFromFile(f);
9966 fseek(f, 0, SEEK_SET);
9967 FREE(appData.participants); appData.participants = participants;
9968 if(expunge) { // erase results of replaced engine
9969 int len = strlen(appData.results), w, b, dummy;
9970 for(i=0; i<len; i++) {
9971 Pairing(i, nPlayers, &w, &b, &dummy);
9972 if((w == changed || b == changed) && appData.results[i] == '*') {
9973 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9978 for(i=0; i<len; i++) {
9979 Pairing(i, nPlayers, &w, &b, &dummy);
9980 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9983 WriteTourneyFile(appData.results, f);
9984 fclose(f); // release lock
9987 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9989 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9990 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9996 CreateTourney (char *name)
9999 if(matchMode && strcmp(name, appData.tourneyFile)) {
10000 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10002 if(name[0] == NULLCHAR) {
10003 if(appData.participants[0])
10004 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10007 f = fopen(name, "r");
10008 if(f) { // file exists
10009 ASSIGN(appData.tourneyFile, name);
10010 ParseArgsFromFile(f); // parse it
10012 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10013 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10014 DisplayError(_("Not enough participants"), 0);
10017 ASSIGN(appData.tourneyFile, name);
10018 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10019 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10022 appData.noChessProgram = FALSE;
10023 appData.clockMode = TRUE;
10029 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10031 char buf[MSG_SIZ], *p, *q;
10032 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10033 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10034 skip = !all && group[0]; // if group requested, we start in skip mode
10035 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10036 p = names; q = buf; header = 0;
10037 while(*p && *p != '\n') *q++ = *p++;
10039 if(*p == '\n') p++;
10040 if(buf[0] == '#') {
10041 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10042 depth++; // we must be entering a new group
10043 if(all) continue; // suppress printing group headers when complete list requested
10045 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10047 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10048 if(engineList[i]) free(engineList[i]);
10049 engineList[i] = strdup(buf);
10050 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10051 if(engineMnemonic[i]) free(engineMnemonic[i]);
10052 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10054 sscanf(q + 8, "%s", buf + strlen(buf));
10057 engineMnemonic[i] = strdup(buf);
10060 engineList[i] = engineMnemonic[i] = NULL;
10064 // following implemented as macro to avoid type limitations
10065 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10068 SwapEngines (int n)
10069 { // swap settings for first engine and other engine (so far only some selected options)
10074 SWAP(chessProgram, p)
10076 SWAP(hasOwnBookUCI, h)
10077 SWAP(protocolVersion, h)
10079 SWAP(scoreIsAbsolute, h)
10084 SWAP(engOptions, p)
10085 SWAP(engInitString, p)
10086 SWAP(computerString, p)
10088 SWAP(fenOverride, p)
10090 SWAP(accumulateTC, h)
10095 SetPlayer (int player, char *p)
10096 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10098 char buf[MSG_SIZ], *engineName;
10099 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10100 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10101 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10103 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10104 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10105 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10106 ParseArgsFromString(buf);
10112 char *recentEngines;
10115 RecentEngineEvent (int nr)
10118 // SwapEngines(1); // bump first to second
10119 // ReplaceEngine(&second, 1); // and load it there
10120 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10121 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10122 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10123 ReplaceEngine(&first, 0);
10124 FloatToFront(&appData.recentEngineList, command[n]);
10129 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10130 { // determine players from game number
10131 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10133 if(appData.tourneyType == 0) {
10134 roundsPerCycle = (nPlayers - 1) | 1;
10135 pairingsPerRound = nPlayers / 2;
10136 } else if(appData.tourneyType > 0) {
10137 roundsPerCycle = nPlayers - appData.tourneyType;
10138 pairingsPerRound = appData.tourneyType;
10140 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10141 gamesPerCycle = gamesPerRound * roundsPerCycle;
10142 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10143 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10144 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10145 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10146 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10147 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10149 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10150 if(appData.roundSync) *syncInterval = gamesPerRound;
10152 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10154 if(appData.tourneyType == 0) {
10155 if(curPairing == (nPlayers-1)/2 ) {
10156 *whitePlayer = curRound;
10157 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10159 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10160 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10161 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10162 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10164 } else if(appData.tourneyType > 1) {
10165 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10166 *whitePlayer = curRound + appData.tourneyType;
10167 } else if(appData.tourneyType > 0) {
10168 *whitePlayer = curPairing;
10169 *blackPlayer = curRound + appData.tourneyType;
10172 // take care of white/black alternation per round.
10173 // For cycles and games this is already taken care of by default, derived from matchGame!
10174 return curRound & 1;
10178 NextTourneyGame (int nr, int *swapColors)
10179 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10181 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10183 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10184 tf = fopen(appData.tourneyFile, "r");
10185 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10186 ParseArgsFromFile(tf); fclose(tf);
10187 InitTimeControls(); // TC might be altered from tourney file
10189 nPlayers = CountPlayers(appData.participants); // count participants
10190 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10191 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10194 p = q = appData.results;
10195 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10196 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10197 DisplayMessage(_("Waiting for other game(s)"),"");
10198 waitingForGame = TRUE;
10199 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10202 waitingForGame = FALSE;
10205 if(appData.tourneyType < 0) {
10206 if(nr>=0 && !pairingReceived) {
10208 if(pairing.pr == NoProc) {
10209 if(!appData.pairingEngine[0]) {
10210 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10213 StartChessProgram(&pairing); // starts the pairing engine
10215 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10216 SendToProgram(buf, &pairing);
10217 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10218 SendToProgram(buf, &pairing);
10219 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10221 pairingReceived = 0; // ... so we continue here
10223 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10224 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10225 matchGame = 1; roundNr = nr / syncInterval + 1;
10228 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10230 // redefine engines, engine dir, etc.
10231 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10232 if(first.pr == NoProc) {
10233 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10234 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10236 if(second.pr == NoProc) {
10238 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10239 SwapEngines(1); // and make that valid for second engine by swapping
10240 InitEngine(&second, 1);
10242 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10243 UpdateLogos(FALSE); // leave display to ModeHiglight()
10249 { // performs game initialization that does not invoke engines, and then tries to start the game
10250 int res, firstWhite, swapColors = 0;
10251 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10252 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
10254 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10255 if(strcmp(buf, currentDebugFile)) { // name has changed
10256 FILE *f = fopen(buf, "w");
10257 if(f) { // if opening the new file failed, just keep using the old one
10258 ASSIGN(currentDebugFile, buf);
10262 if(appData.serverFileName) {
10263 if(serverFP) fclose(serverFP);
10264 serverFP = fopen(appData.serverFileName, "w");
10265 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10266 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10270 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10271 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10272 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10273 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10274 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10275 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10276 Reset(FALSE, first.pr != NoProc);
10277 res = LoadGameOrPosition(matchGame); // setup game
10278 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10279 if(!res) return; // abort when bad game/pos file
10280 TwoMachinesEvent();
10284 UserAdjudicationEvent (int result)
10286 ChessMove gameResult = GameIsDrawn;
10289 gameResult = WhiteWins;
10291 else if( result < 0 ) {
10292 gameResult = BlackWins;
10295 if( gameMode == TwoMachinesPlay ) {
10296 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10301 // [HGM] save: calculate checksum of game to make games easily identifiable
10303 StringCheckSum (char *s)
10306 if(s==NULL) return 0;
10307 while(*s) i = i*259 + *s++;
10315 for(i=backwardMostMove; i<forwardMostMove; i++) {
10316 sum += pvInfoList[i].depth;
10317 sum += StringCheckSum(parseList[i]);
10318 sum += StringCheckSum(commentList[i]);
10321 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10322 return sum + StringCheckSum(commentList[i]);
10323 } // end of save patch
10326 GameEnds (ChessMove result, char *resultDetails, int whosays)
10328 GameMode nextGameMode;
10330 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10332 if(endingGame) return; /* [HGM] crash: forbid recursion */
10334 if(twoBoards) { // [HGM] dual: switch back to one board
10335 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10336 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10338 if (appData.debugMode) {
10339 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10340 result, resultDetails ? resultDetails : "(null)", whosays);
10343 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10345 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10346 /* If we are playing on ICS, the server decides when the
10347 game is over, but the engine can offer to draw, claim
10351 if (appData.zippyPlay && first.initDone) {
10352 if (result == GameIsDrawn) {
10353 /* In case draw still needs to be claimed */
10354 SendToICS(ics_prefix);
10355 SendToICS("draw\n");
10356 } else if (StrCaseStr(resultDetails, "resign")) {
10357 SendToICS(ics_prefix);
10358 SendToICS("resign\n");
10362 endingGame = 0; /* [HGM] crash */
10366 /* If we're loading the game from a file, stop */
10367 if (whosays == GE_FILE) {
10368 (void) StopLoadGameTimer();
10372 /* Cancel draw offers */
10373 first.offeredDraw = second.offeredDraw = 0;
10375 /* If this is an ICS game, only ICS can really say it's done;
10376 if not, anyone can. */
10377 isIcsGame = (gameMode == IcsPlayingWhite ||
10378 gameMode == IcsPlayingBlack ||
10379 gameMode == IcsObserving ||
10380 gameMode == IcsExamining);
10382 if (!isIcsGame || whosays == GE_ICS) {
10383 /* OK -- not an ICS game, or ICS said it was done */
10385 if (!isIcsGame && !appData.noChessProgram)
10386 SetUserThinkingEnables();
10388 /* [HGM] if a machine claims the game end we verify this claim */
10389 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10390 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10392 ChessMove trueResult = (ChessMove) -1;
10394 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10395 first.twoMachinesColor[0] :
10396 second.twoMachinesColor[0] ;
10398 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10399 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10400 /* [HGM] verify: engine mate claims accepted if they were flagged */
10401 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10403 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10404 /* [HGM] verify: engine mate claims accepted if they were flagged */
10405 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10407 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10408 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10411 // now verify win claims, but not in drop games, as we don't understand those yet
10412 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10413 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10414 (result == WhiteWins && claimer == 'w' ||
10415 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10416 if (appData.debugMode) {
10417 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10418 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10420 if(result != trueResult) {
10421 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10422 result = claimer == 'w' ? BlackWins : WhiteWins;
10423 resultDetails = buf;
10426 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10427 && (forwardMostMove <= backwardMostMove ||
10428 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10429 (claimer=='b')==(forwardMostMove&1))
10431 /* [HGM] verify: draws that were not flagged are false claims */
10432 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10433 result = claimer == 'w' ? BlackWins : WhiteWins;
10434 resultDetails = buf;
10436 /* (Claiming a loss is accepted no questions asked!) */
10438 /* [HGM] bare: don't allow bare King to win */
10439 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10440 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10441 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10442 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10443 && result != GameIsDrawn)
10444 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10445 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10446 int p = (signed char)boards[forwardMostMove][i][j] - color;
10447 if(p >= 0 && p <= (int)WhiteKing) k++;
10449 if (appData.debugMode) {
10450 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10451 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10454 result = GameIsDrawn;
10455 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10456 resultDetails = buf;
10462 if(serverMoves != NULL && !loadFlag) { char c = '=';
10463 if(result==WhiteWins) c = '+';
10464 if(result==BlackWins) c = '-';
10465 if(resultDetails != NULL)
10466 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10468 if (resultDetails != NULL) {
10469 gameInfo.result = result;
10470 gameInfo.resultDetails = StrSave(resultDetails);
10472 /* display last move only if game was not loaded from file */
10473 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10474 DisplayMove(currentMove - 1);
10476 if (forwardMostMove != 0) {
10477 if (gameMode != PlayFromGameFile && gameMode != EditGame
10478 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10480 if (*appData.saveGameFile != NULLCHAR) {
10481 SaveGameToFile(appData.saveGameFile, TRUE);
10482 } else if (appData.autoSaveGames) {
10485 if (*appData.savePositionFile != NULLCHAR) {
10486 SavePositionToFile(appData.savePositionFile);
10491 /* Tell program how game ended in case it is learning */
10492 /* [HGM] Moved this to after saving the PGN, just in case */
10493 /* engine died and we got here through time loss. In that */
10494 /* case we will get a fatal error writing the pipe, which */
10495 /* would otherwise lose us the PGN. */
10496 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10497 /* output during GameEnds should never be fatal anymore */
10498 if (gameMode == MachinePlaysWhite ||
10499 gameMode == MachinePlaysBlack ||
10500 gameMode == TwoMachinesPlay ||
10501 gameMode == IcsPlayingWhite ||
10502 gameMode == IcsPlayingBlack ||
10503 gameMode == BeginningOfGame) {
10505 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10507 if (first.pr != NoProc) {
10508 SendToProgram(buf, &first);
10510 if (second.pr != NoProc &&
10511 gameMode == TwoMachinesPlay) {
10512 SendToProgram(buf, &second);
10517 if (appData.icsActive) {
10518 if (appData.quietPlay &&
10519 (gameMode == IcsPlayingWhite ||
10520 gameMode == IcsPlayingBlack)) {
10521 SendToICS(ics_prefix);
10522 SendToICS("set shout 1\n");
10524 nextGameMode = IcsIdle;
10525 ics_user_moved = FALSE;
10526 /* clean up premove. It's ugly when the game has ended and the
10527 * premove highlights are still on the board.
10530 gotPremove = FALSE;
10531 ClearPremoveHighlights();
10532 DrawPosition(FALSE, boards[currentMove]);
10534 if (whosays == GE_ICS) {
10537 if (gameMode == IcsPlayingWhite)
10539 else if(gameMode == IcsPlayingBlack)
10540 PlayIcsLossSound();
10543 if (gameMode == IcsPlayingBlack)
10545 else if(gameMode == IcsPlayingWhite)
10546 PlayIcsLossSound();
10549 PlayIcsDrawSound();
10552 PlayIcsUnfinishedSound();
10555 } else if (gameMode == EditGame ||
10556 gameMode == PlayFromGameFile ||
10557 gameMode == AnalyzeMode ||
10558 gameMode == AnalyzeFile) {
10559 nextGameMode = gameMode;
10561 nextGameMode = EndOfGame;
10566 nextGameMode = gameMode;
10569 if (appData.noChessProgram) {
10570 gameMode = nextGameMode;
10572 endingGame = 0; /* [HGM] crash */
10577 /* Put first chess program into idle state */
10578 if (first.pr != NoProc &&
10579 (gameMode == MachinePlaysWhite ||
10580 gameMode == MachinePlaysBlack ||
10581 gameMode == TwoMachinesPlay ||
10582 gameMode == IcsPlayingWhite ||
10583 gameMode == IcsPlayingBlack ||
10584 gameMode == BeginningOfGame)) {
10585 SendToProgram("force\n", &first);
10586 if (first.usePing) {
10588 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10589 SendToProgram(buf, &first);
10592 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10593 /* Kill off first chess program */
10594 if (first.isr != NULL)
10595 RemoveInputSource(first.isr);
10598 if (first.pr != NoProc) {
10600 DoSleep( appData.delayBeforeQuit );
10601 SendToProgram("quit\n", &first);
10602 DoSleep( appData.delayAfterQuit );
10603 DestroyChildProcess(first.pr, first.useSigterm);
10607 if (second.reuse) {
10608 /* Put second chess program into idle state */
10609 if (second.pr != NoProc &&
10610 gameMode == TwoMachinesPlay) {
10611 SendToProgram("force\n", &second);
10612 if (second.usePing) {
10614 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10615 SendToProgram(buf, &second);
10618 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10619 /* Kill off second chess program */
10620 if (second.isr != NULL)
10621 RemoveInputSource(second.isr);
10624 if (second.pr != NoProc) {
10625 DoSleep( appData.delayBeforeQuit );
10626 SendToProgram("quit\n", &second);
10627 DoSleep( appData.delayAfterQuit );
10628 DestroyChildProcess(second.pr, second.useSigterm);
10630 second.pr = NoProc;
10633 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10634 char resChar = '=';
10638 if (first.twoMachinesColor[0] == 'w') {
10641 second.matchWins++;
10646 if (first.twoMachinesColor[0] == 'b') {
10649 second.matchWins++;
10652 case GameUnfinished:
10658 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10659 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10660 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10661 ReserveGame(nextGame, resChar); // sets nextGame
10662 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10663 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10664 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10666 if (nextGame <= appData.matchGames && !abortMatch) {
10667 gameMode = nextGameMode;
10668 matchGame = nextGame; // this will be overruled in tourney mode!
10669 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10670 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10671 endingGame = 0; /* [HGM] crash */
10674 gameMode = nextGameMode;
10675 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10676 first.tidy, second.tidy,
10677 first.matchWins, second.matchWins,
10678 appData.matchGames - (first.matchWins + second.matchWins));
10679 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10680 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10681 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10682 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10683 first.twoMachinesColor = "black\n";
10684 second.twoMachinesColor = "white\n";
10686 first.twoMachinesColor = "white\n";
10687 second.twoMachinesColor = "black\n";
10691 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10692 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10694 gameMode = nextGameMode;
10696 endingGame = 0; /* [HGM] crash */
10697 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10698 if(matchMode == TRUE) { // match through command line: exit with or without popup
10700 ToNrEvent(forwardMostMove);
10701 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10703 } else DisplayFatalError(buf, 0, 0);
10704 } else { // match through menu; just stop, with or without popup
10705 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10708 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10709 } else DisplayNote(buf);
10711 if(ranking) free(ranking);
10715 /* Assumes program was just initialized (initString sent).
10716 Leaves program in force mode. */
10718 FeedMovesToProgram (ChessProgramState *cps, int upto)
10722 if (appData.debugMode)
10723 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10724 startedFromSetupPosition ? "position and " : "",
10725 backwardMostMove, upto, cps->which);
10726 if(currentlyInitializedVariant != gameInfo.variant) {
10728 // [HGM] variantswitch: make engine aware of new variant
10729 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10730 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10731 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10732 SendToProgram(buf, cps);
10733 currentlyInitializedVariant = gameInfo.variant;
10735 SendToProgram("force\n", cps);
10736 if (startedFromSetupPosition) {
10737 SendBoard(cps, backwardMostMove);
10738 if (appData.debugMode) {
10739 fprintf(debugFP, "feedMoves\n");
10742 for (i = backwardMostMove; i < upto; i++) {
10743 SendMoveToProgram(i, cps);
10749 ResurrectChessProgram ()
10751 /* The chess program may have exited.
10752 If so, restart it and feed it all the moves made so far. */
10753 static int doInit = 0;
10755 if (appData.noChessProgram) return 1;
10757 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10758 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10759 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10760 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10762 if (first.pr != NoProc) return 1;
10763 StartChessProgram(&first);
10765 InitChessProgram(&first, FALSE);
10766 FeedMovesToProgram(&first, currentMove);
10768 if (!first.sendTime) {
10769 /* can't tell gnuchess what its clock should read,
10770 so we bow to its notion. */
10772 timeRemaining[0][currentMove] = whiteTimeRemaining;
10773 timeRemaining[1][currentMove] = blackTimeRemaining;
10776 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10777 appData.icsEngineAnalyze) && first.analysisSupport) {
10778 SendToProgram("analyze\n", &first);
10779 first.analyzing = TRUE;
10785 * Button procedures
10788 Reset (int redraw, int init)
10792 if (appData.debugMode) {
10793 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10794 redraw, init, gameMode);
10796 CleanupTail(); // [HGM] vari: delete any stored variations
10797 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10798 pausing = pauseExamInvalid = FALSE;
10799 startedFromSetupPosition = blackPlaysFirst = FALSE;
10801 whiteFlag = blackFlag = FALSE;
10802 userOfferedDraw = FALSE;
10803 hintRequested = bookRequested = FALSE;
10804 first.maybeThinking = FALSE;
10805 second.maybeThinking = FALSE;
10806 first.bookSuspend = FALSE; // [HGM] book
10807 second.bookSuspend = FALSE;
10808 thinkOutput[0] = NULLCHAR;
10809 lastHint[0] = NULLCHAR;
10810 ClearGameInfo(&gameInfo);
10811 gameInfo.variant = StringToVariant(appData.variant);
10812 ics_user_moved = ics_clock_paused = FALSE;
10813 ics_getting_history = H_FALSE;
10815 white_holding[0] = black_holding[0] = NULLCHAR;
10816 ClearProgramStats();
10817 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10821 flipView = appData.flipView;
10822 ClearPremoveHighlights();
10823 gotPremove = FALSE;
10824 alarmSounded = FALSE;
10826 GameEnds(EndOfFile, NULL, GE_PLAYER);
10827 if(appData.serverMovesName != NULL) {
10828 /* [HGM] prepare to make moves file for broadcasting */
10829 clock_t t = clock();
10830 if(serverMoves != NULL) fclose(serverMoves);
10831 serverMoves = fopen(appData.serverMovesName, "r");
10832 if(serverMoves != NULL) {
10833 fclose(serverMoves);
10834 /* delay 15 sec before overwriting, so all clients can see end */
10835 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10837 serverMoves = fopen(appData.serverMovesName, "w");
10841 gameMode = BeginningOfGame;
10843 if(appData.icsActive) gameInfo.variant = VariantNormal;
10844 currentMove = forwardMostMove = backwardMostMove = 0;
10845 MarkTargetSquares(1);
10846 InitPosition(redraw);
10847 for (i = 0; i < MAX_MOVES; i++) {
10848 if (commentList[i] != NULL) {
10849 free(commentList[i]);
10850 commentList[i] = NULL;
10854 timeRemaining[0][0] = whiteTimeRemaining;
10855 timeRemaining[1][0] = blackTimeRemaining;
10857 if (first.pr == NoProc) {
10858 StartChessProgram(&first);
10861 InitChessProgram(&first, startedFromSetupPosition);
10864 DisplayMessage("", "");
10865 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10866 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10867 ClearMap(); // [HGM] exclude: invalidate map
10871 AutoPlayGameLoop ()
10874 if (!AutoPlayOneMove())
10876 if (matchMode || appData.timeDelay == 0)
10878 if (appData.timeDelay < 0)
10880 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10889 int fromX, fromY, toX, toY;
10891 if (appData.debugMode) {
10892 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10895 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10898 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10899 pvInfoList[currentMove].depth = programStats.depth;
10900 pvInfoList[currentMove].score = programStats.score;
10901 pvInfoList[currentMove].time = 0;
10902 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10905 if (currentMove >= forwardMostMove) {
10906 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10907 // gameMode = EndOfGame;
10908 // ModeHighlight();
10910 /* [AS] Clear current move marker at the end of a game */
10911 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10916 toX = moveList[currentMove][2] - AAA;
10917 toY = moveList[currentMove][3] - ONE;
10919 if (moveList[currentMove][1] == '@') {
10920 if (appData.highlightLastMove) {
10921 SetHighlights(-1, -1, toX, toY);
10924 fromX = moveList[currentMove][0] - AAA;
10925 fromY = moveList[currentMove][1] - ONE;
10927 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10929 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10931 if (appData.highlightLastMove) {
10932 SetHighlights(fromX, fromY, toX, toY);
10935 DisplayMove(currentMove);
10936 SendMoveToProgram(currentMove++, &first);
10937 DisplayBothClocks();
10938 DrawPosition(FALSE, boards[currentMove]);
10939 // [HGM] PV info: always display, routine tests if empty
10940 DisplayComment(currentMove - 1, commentList[currentMove]);
10946 LoadGameOneMove (ChessMove readAhead)
10948 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10949 char promoChar = NULLCHAR;
10950 ChessMove moveType;
10951 char move[MSG_SIZ];
10954 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10955 gameMode != AnalyzeMode && gameMode != Training) {
10960 yyboardindex = forwardMostMove;
10961 if (readAhead != EndOfFile) {
10962 moveType = readAhead;
10964 if (gameFileFP == NULL)
10966 moveType = (ChessMove) Myylex();
10970 switch (moveType) {
10972 if (appData.debugMode)
10973 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10976 /* append the comment but don't display it */
10977 AppendComment(currentMove, p, FALSE);
10980 case WhiteCapturesEnPassant:
10981 case BlackCapturesEnPassant:
10982 case WhitePromotion:
10983 case BlackPromotion:
10984 case WhiteNonPromotion:
10985 case BlackNonPromotion:
10987 case WhiteKingSideCastle:
10988 case WhiteQueenSideCastle:
10989 case BlackKingSideCastle:
10990 case BlackQueenSideCastle:
10991 case WhiteKingSideCastleWild:
10992 case WhiteQueenSideCastleWild:
10993 case BlackKingSideCastleWild:
10994 case BlackQueenSideCastleWild:
10996 case WhiteHSideCastleFR:
10997 case WhiteASideCastleFR:
10998 case BlackHSideCastleFR:
10999 case BlackASideCastleFR:
11001 if (appData.debugMode)
11002 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11003 fromX = currentMoveString[0] - AAA;
11004 fromY = currentMoveString[1] - ONE;
11005 toX = currentMoveString[2] - AAA;
11006 toY = currentMoveString[3] - ONE;
11007 promoChar = currentMoveString[4];
11012 if (appData.debugMode)
11013 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11014 fromX = moveType == WhiteDrop ?
11015 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11016 (int) CharToPiece(ToLower(currentMoveString[0]));
11018 toX = currentMoveString[2] - AAA;
11019 toY = currentMoveString[3] - ONE;
11025 case GameUnfinished:
11026 if (appData.debugMode)
11027 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11028 p = strchr(yy_text, '{');
11029 if (p == NULL) p = strchr(yy_text, '(');
11032 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11034 q = strchr(p, *p == '{' ? '}' : ')');
11035 if (q != NULL) *q = NULLCHAR;
11038 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11039 GameEnds(moveType, p, GE_FILE);
11041 if (cmailMsgLoaded) {
11043 flipView = WhiteOnMove(currentMove);
11044 if (moveType == GameUnfinished) flipView = !flipView;
11045 if (appData.debugMode)
11046 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11051 if (appData.debugMode)
11052 fprintf(debugFP, "Parser hit end of file\n");
11053 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11059 if (WhiteOnMove(currentMove)) {
11060 GameEnds(BlackWins, "Black mates", GE_FILE);
11062 GameEnds(WhiteWins, "White mates", GE_FILE);
11066 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11072 case MoveNumberOne:
11073 if (lastLoadGameStart == GNUChessGame) {
11074 /* GNUChessGames have numbers, but they aren't move numbers */
11075 if (appData.debugMode)
11076 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11077 yy_text, (int) moveType);
11078 return LoadGameOneMove(EndOfFile); /* tail recursion */
11080 /* else fall thru */
11085 /* Reached start of next game in file */
11086 if (appData.debugMode)
11087 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11088 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11094 if (WhiteOnMove(currentMove)) {
11095 GameEnds(BlackWins, "Black mates", GE_FILE);
11097 GameEnds(WhiteWins, "White mates", GE_FILE);
11101 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11107 case PositionDiagram: /* should not happen; ignore */
11108 case ElapsedTime: /* ignore */
11109 case NAG: /* ignore */
11110 if (appData.debugMode)
11111 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11112 yy_text, (int) moveType);
11113 return LoadGameOneMove(EndOfFile); /* tail recursion */
11116 if (appData.testLegality) {
11117 if (appData.debugMode)
11118 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11119 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11120 (forwardMostMove / 2) + 1,
11121 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11122 DisplayError(move, 0);
11125 if (appData.debugMode)
11126 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11127 yy_text, currentMoveString);
11128 fromX = currentMoveString[0] - AAA;
11129 fromY = currentMoveString[1] - ONE;
11130 toX = currentMoveString[2] - AAA;
11131 toY = currentMoveString[3] - ONE;
11132 promoChar = currentMoveString[4];
11136 case AmbiguousMove:
11137 if (appData.debugMode)
11138 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11139 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11140 (forwardMostMove / 2) + 1,
11141 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11142 DisplayError(move, 0);
11147 case ImpossibleMove:
11148 if (appData.debugMode)
11149 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11150 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11151 (forwardMostMove / 2) + 1,
11152 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11153 DisplayError(move, 0);
11159 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11160 DrawPosition(FALSE, boards[currentMove]);
11161 DisplayBothClocks();
11162 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11163 DisplayComment(currentMove - 1, commentList[currentMove]);
11165 (void) StopLoadGameTimer();
11167 cmailOldMove = forwardMostMove;
11170 /* currentMoveString is set as a side-effect of yylex */
11172 thinkOutput[0] = NULLCHAR;
11173 MakeMove(fromX, fromY, toX, toY, promoChar);
11174 currentMove = forwardMostMove;
11179 /* Load the nth game from the given file */
11181 LoadGameFromFile (char *filename, int n, char *title, int useList)
11186 if (strcmp(filename, "-") == 0) {
11190 f = fopen(filename, "rb");
11192 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11193 DisplayError(buf, errno);
11197 if (fseek(f, 0, 0) == -1) {
11198 /* f is not seekable; probably a pipe */
11201 if (useList && n == 0) {
11202 int error = GameListBuild(f);
11204 DisplayError(_("Cannot build game list"), error);
11205 } else if (!ListEmpty(&gameList) &&
11206 ((ListGame *) gameList.tailPred)->number > 1) {
11207 GameListPopUp(f, title);
11214 return LoadGame(f, n, title, FALSE);
11219 MakeRegisteredMove ()
11221 int fromX, fromY, toX, toY;
11223 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11224 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11227 if (appData.debugMode)
11228 fprintf(debugFP, "Restoring %s for game %d\n",
11229 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11231 thinkOutput[0] = NULLCHAR;
11232 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11233 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11234 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11235 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11236 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11237 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11238 MakeMove(fromX, fromY, toX, toY, promoChar);
11239 ShowMove(fromX, fromY, toX, toY);
11241 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11248 if (WhiteOnMove(currentMove)) {
11249 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11251 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11256 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11263 if (WhiteOnMove(currentMove)) {
11264 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11266 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11271 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11282 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11284 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11288 if (gameNumber > nCmailGames) {
11289 DisplayError(_("No more games in this message"), 0);
11292 if (f == lastLoadGameFP) {
11293 int offset = gameNumber - lastLoadGameNumber;
11295 cmailMsg[0] = NULLCHAR;
11296 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11297 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11298 nCmailMovesRegistered--;
11300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11301 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11302 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11305 if (! RegisterMove()) return FALSE;
11309 retVal = LoadGame(f, gameNumber, title, useList);
11311 /* Make move registered during previous look at this game, if any */
11312 MakeRegisteredMove();
11314 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11315 commentList[currentMove]
11316 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11317 DisplayComment(currentMove - 1, commentList[currentMove]);
11323 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11325 ReloadGame (int offset)
11327 int gameNumber = lastLoadGameNumber + offset;
11328 if (lastLoadGameFP == NULL) {
11329 DisplayError(_("No game has been loaded yet"), 0);
11332 if (gameNumber <= 0) {
11333 DisplayError(_("Can't back up any further"), 0);
11336 if (cmailMsgLoaded) {
11337 return CmailLoadGame(lastLoadGameFP, gameNumber,
11338 lastLoadGameTitle, lastLoadGameUseList);
11340 return LoadGame(lastLoadGameFP, gameNumber,
11341 lastLoadGameTitle, lastLoadGameUseList);
11345 int keys[EmptySquare+1];
11348 PositionMatches (Board b1, Board b2)
11351 switch(appData.searchMode) {
11352 case 1: return CompareWithRights(b1, b2);
11354 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11355 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11359 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11360 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11361 sum += keys[b1[r][f]] - keys[b2[r][f]];
11365 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11366 sum += keys[b1[r][f]] - keys[b2[r][f]];
11378 int pieceList[256], quickBoard[256];
11379 ChessSquare pieceType[256] = { EmptySquare };
11380 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11381 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11382 int soughtTotal, turn;
11383 Boolean epOK, flipSearch;
11386 unsigned char piece, to;
11389 #define DSIZE (250000)
11391 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11392 Move *moveDatabase = initialSpace;
11393 unsigned int movePtr, dataSize = DSIZE;
11396 MakePieceList (Board board, int *counts)
11398 int r, f, n=Q_PROMO, total=0;
11399 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11400 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11401 int sq = f + (r<<4);
11402 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11403 quickBoard[sq] = ++n;
11405 pieceType[n] = board[r][f];
11406 counts[board[r][f]]++;
11407 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11408 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11412 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11417 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11419 int sq = fromX + (fromY<<4);
11420 int piece = quickBoard[sq];
11421 quickBoard[sq] = 0;
11422 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11423 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11424 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11425 moveDatabase[movePtr++].piece = Q_WCASTL;
11426 quickBoard[sq] = piece;
11427 piece = quickBoard[from]; quickBoard[from] = 0;
11428 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11430 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11431 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11432 moveDatabase[movePtr++].piece = Q_BCASTL;
11433 quickBoard[sq] = piece;
11434 piece = quickBoard[from]; quickBoard[from] = 0;
11435 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11437 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11438 quickBoard[(fromY<<4)+toX] = 0;
11439 moveDatabase[movePtr].piece = Q_EP;
11440 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11441 moveDatabase[movePtr].to = sq;
11443 if(promoPiece != pieceType[piece]) {
11444 moveDatabase[movePtr++].piece = Q_PROMO;
11445 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11447 moveDatabase[movePtr].piece = piece;
11448 quickBoard[sq] = piece;
11453 PackGame (Board board)
11455 Move *newSpace = NULL;
11456 moveDatabase[movePtr].piece = 0; // terminate previous game
11457 if(movePtr > dataSize) {
11458 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11459 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11460 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11463 Move *p = moveDatabase, *q = newSpace;
11464 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11465 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11466 moveDatabase = newSpace;
11467 } else { // calloc failed, we must be out of memory. Too bad...
11468 dataSize = 0; // prevent calloc events for all subsequent games
11469 return 0; // and signal this one isn't cached
11473 MakePieceList(board, counts);
11478 QuickCompare (Board board, int *minCounts, int *maxCounts)
11479 { // compare according to search mode
11481 switch(appData.searchMode)
11483 case 1: // exact position match
11484 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11485 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11486 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11489 case 2: // can have extra material on empty squares
11490 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11491 if(board[r][f] == EmptySquare) continue;
11492 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11495 case 3: // material with exact Pawn structure
11496 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11497 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11498 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11499 } // fall through to material comparison
11500 case 4: // exact material
11501 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11503 case 6: // material range with given imbalance
11504 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11505 // fall through to range comparison
11506 case 5: // material range
11507 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11513 QuickScan (Board board, Move *move)
11514 { // reconstruct game,and compare all positions in it
11515 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11517 int piece = move->piece;
11518 int to = move->to, from = pieceList[piece];
11519 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11520 if(!piece) return -1;
11521 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11522 piece = (++move)->piece;
11523 from = pieceList[piece];
11524 counts[pieceType[piece]]--;
11525 pieceType[piece] = (ChessSquare) move->to;
11526 counts[move->to]++;
11527 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11528 counts[pieceType[quickBoard[to]]]--;
11529 quickBoard[to] = 0; total--;
11532 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11533 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11534 from = pieceList[piece]; // so this must be King
11535 quickBoard[from] = 0;
11536 quickBoard[to] = piece;
11537 pieceList[piece] = to;
11542 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11543 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11544 quickBoard[from] = 0;
11545 quickBoard[to] = piece;
11546 pieceList[piece] = to;
11548 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11549 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11550 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11551 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11553 static int lastCounts[EmptySquare+1];
11555 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11556 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11557 } else stretch = 0;
11558 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11567 flipSearch = FALSE;
11568 CopyBoard(soughtBoard, boards[currentMove]);
11569 soughtTotal = MakePieceList(soughtBoard, maxSought);
11570 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11571 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11572 CopyBoard(reverseBoard, boards[currentMove]);
11573 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11574 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11575 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11576 reverseBoard[r][f] = piece;
11578 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11579 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11580 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11581 || (boards[currentMove][CASTLING][2] == NoRights ||
11582 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11583 && (boards[currentMove][CASTLING][5] == NoRights ||
11584 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11587 CopyBoard(flipBoard, soughtBoard);
11588 CopyBoard(rotateBoard, reverseBoard);
11589 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11590 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11591 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11594 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11595 if(appData.searchMode >= 5) {
11596 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11597 MakePieceList(soughtBoard, minSought);
11598 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11600 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11601 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11604 GameInfo dummyInfo;
11607 GameContainsPosition (FILE *f, ListGame *lg)
11609 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11610 int fromX, fromY, toX, toY;
11612 static int initDone=FALSE;
11614 // weed out games based on numerical tag comparison
11615 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11616 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11617 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11618 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11620 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11623 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11624 else CopyBoard(boards[scratch], initialPosition); // default start position
11627 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11628 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11631 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11632 fseek(f, lg->offset, 0);
11635 yyboardindex = scratch;
11636 quickFlag = plyNr+1;
11641 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11647 if(plyNr) return -1; // after we have seen moves, this is for new game
11650 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11651 case ImpossibleMove:
11652 case WhiteWins: // game ends here with these four
11655 case GameUnfinished:
11659 if(appData.testLegality) return -1;
11660 case WhiteCapturesEnPassant:
11661 case BlackCapturesEnPassant:
11662 case WhitePromotion:
11663 case BlackPromotion:
11664 case WhiteNonPromotion:
11665 case BlackNonPromotion:
11667 case WhiteKingSideCastle:
11668 case WhiteQueenSideCastle:
11669 case BlackKingSideCastle:
11670 case BlackQueenSideCastle:
11671 case WhiteKingSideCastleWild:
11672 case WhiteQueenSideCastleWild:
11673 case BlackKingSideCastleWild:
11674 case BlackQueenSideCastleWild:
11675 case WhiteHSideCastleFR:
11676 case WhiteASideCastleFR:
11677 case BlackHSideCastleFR:
11678 case BlackASideCastleFR:
11679 fromX = currentMoveString[0] - AAA;
11680 fromY = currentMoveString[1] - ONE;
11681 toX = currentMoveString[2] - AAA;
11682 toY = currentMoveString[3] - ONE;
11683 promoChar = currentMoveString[4];
11687 fromX = next == WhiteDrop ?
11688 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11689 (int) CharToPiece(ToLower(currentMoveString[0]));
11691 toX = currentMoveString[2] - AAA;
11692 toY = currentMoveString[3] - ONE;
11696 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11698 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11699 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11700 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11701 if(appData.findMirror) {
11702 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11703 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11708 /* Load the nth game from open file f */
11710 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11714 int gn = gameNumber;
11715 ListGame *lg = NULL;
11716 int numPGNTags = 0;
11718 GameMode oldGameMode;
11719 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11721 if (appData.debugMode)
11722 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11724 if (gameMode == Training )
11725 SetTrainingModeOff();
11727 oldGameMode = gameMode;
11728 if (gameMode != BeginningOfGame) {
11729 Reset(FALSE, TRUE);
11733 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11734 fclose(lastLoadGameFP);
11738 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11741 fseek(f, lg->offset, 0);
11742 GameListHighlight(gameNumber);
11743 pos = lg->position;
11747 DisplayError(_("Game number out of range"), 0);
11752 if (fseek(f, 0, 0) == -1) {
11753 if (f == lastLoadGameFP ?
11754 gameNumber == lastLoadGameNumber + 1 :
11758 DisplayError(_("Can't seek on game file"), 0);
11763 lastLoadGameFP = f;
11764 lastLoadGameNumber = gameNumber;
11765 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11766 lastLoadGameUseList = useList;
11770 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11771 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11772 lg->gameInfo.black);
11774 } else if (*title != NULLCHAR) {
11775 if (gameNumber > 1) {
11776 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11779 DisplayTitle(title);
11783 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11784 gameMode = PlayFromGameFile;
11788 currentMove = forwardMostMove = backwardMostMove = 0;
11789 CopyBoard(boards[0], initialPosition);
11793 * Skip the first gn-1 games in the file.
11794 * Also skip over anything that precedes an identifiable
11795 * start of game marker, to avoid being confused by
11796 * garbage at the start of the file. Currently
11797 * recognized start of game markers are the move number "1",
11798 * the pattern "gnuchess .* game", the pattern
11799 * "^[#;%] [^ ]* game file", and a PGN tag block.
11800 * A game that starts with one of the latter two patterns
11801 * will also have a move number 1, possibly
11802 * following a position diagram.
11803 * 5-4-02: Let's try being more lenient and allowing a game to
11804 * start with an unnumbered move. Does that break anything?
11806 cm = lastLoadGameStart = EndOfFile;
11808 yyboardindex = forwardMostMove;
11809 cm = (ChessMove) Myylex();
11812 if (cmailMsgLoaded) {
11813 nCmailGames = CMAIL_MAX_GAMES - gn;
11816 DisplayError(_("Game not found in file"), 0);
11823 lastLoadGameStart = cm;
11826 case MoveNumberOne:
11827 switch (lastLoadGameStart) {
11832 case MoveNumberOne:
11834 gn--; /* count this game */
11835 lastLoadGameStart = cm;
11844 switch (lastLoadGameStart) {
11847 case MoveNumberOne:
11849 gn--; /* count this game */
11850 lastLoadGameStart = cm;
11853 lastLoadGameStart = cm; /* game counted already */
11861 yyboardindex = forwardMostMove;
11862 cm = (ChessMove) Myylex();
11863 } while (cm == PGNTag || cm == Comment);
11870 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11871 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11872 != CMAIL_OLD_RESULT) {
11874 cmailResult[ CMAIL_MAX_GAMES
11875 - gn - 1] = CMAIL_OLD_RESULT;
11881 /* Only a NormalMove can be at the start of a game
11882 * without a position diagram. */
11883 if (lastLoadGameStart == EndOfFile ) {
11885 lastLoadGameStart = MoveNumberOne;
11894 if (appData.debugMode)
11895 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11897 if (cm == XBoardGame) {
11898 /* Skip any header junk before position diagram and/or move 1 */
11900 yyboardindex = forwardMostMove;
11901 cm = (ChessMove) Myylex();
11903 if (cm == EndOfFile ||
11904 cm == GNUChessGame || cm == XBoardGame) {
11905 /* Empty game; pretend end-of-file and handle later */
11910 if (cm == MoveNumberOne || cm == PositionDiagram ||
11911 cm == PGNTag || cm == Comment)
11914 } else if (cm == GNUChessGame) {
11915 if (gameInfo.event != NULL) {
11916 free(gameInfo.event);
11918 gameInfo.event = StrSave(yy_text);
11921 startedFromSetupPosition = FALSE;
11922 while (cm == PGNTag) {
11923 if (appData.debugMode)
11924 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11925 err = ParsePGNTag(yy_text, &gameInfo);
11926 if (!err) numPGNTags++;
11928 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11929 if(gameInfo.variant != oldVariant) {
11930 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11931 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11932 InitPosition(TRUE);
11933 oldVariant = gameInfo.variant;
11934 if (appData.debugMode)
11935 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11939 if (gameInfo.fen != NULL) {
11940 Board initial_position;
11941 startedFromSetupPosition = TRUE;
11942 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11944 DisplayError(_("Bad FEN position in file"), 0);
11947 CopyBoard(boards[0], initial_position);
11948 if (blackPlaysFirst) {
11949 currentMove = forwardMostMove = backwardMostMove = 1;
11950 CopyBoard(boards[1], initial_position);
11951 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11952 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11953 timeRemaining[0][1] = whiteTimeRemaining;
11954 timeRemaining[1][1] = blackTimeRemaining;
11955 if (commentList[0] != NULL) {
11956 commentList[1] = commentList[0];
11957 commentList[0] = NULL;
11960 currentMove = forwardMostMove = backwardMostMove = 0;
11962 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11964 initialRulePlies = FENrulePlies;
11965 for( i=0; i< nrCastlingRights; i++ )
11966 initialRights[i] = initial_position[CASTLING][i];
11968 yyboardindex = forwardMostMove;
11969 free(gameInfo.fen);
11970 gameInfo.fen = NULL;
11973 yyboardindex = forwardMostMove;
11974 cm = (ChessMove) Myylex();
11976 /* Handle comments interspersed among the tags */
11977 while (cm == Comment) {
11979 if (appData.debugMode)
11980 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11982 AppendComment(currentMove, p, FALSE);
11983 yyboardindex = forwardMostMove;
11984 cm = (ChessMove) Myylex();
11988 /* don't rely on existence of Event tag since if game was
11989 * pasted from clipboard the Event tag may not exist
11991 if (numPGNTags > 0){
11993 if (gameInfo.variant == VariantNormal) {
11994 VariantClass v = StringToVariant(gameInfo.event);
11995 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11996 if(v < VariantShogi) gameInfo.variant = v;
11999 if( appData.autoDisplayTags ) {
12000 tags = PGNTags(&gameInfo);
12001 TagsPopUp(tags, CmailMsg());
12006 /* Make something up, but don't display it now */
12011 if (cm == PositionDiagram) {
12014 Board initial_position;
12016 if (appData.debugMode)
12017 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12019 if (!startedFromSetupPosition) {
12021 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12022 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12033 initial_position[i][j++] = CharToPiece(*p);
12036 while (*p == ' ' || *p == '\t' ||
12037 *p == '\n' || *p == '\r') p++;
12039 if (strncmp(p, "black", strlen("black"))==0)
12040 blackPlaysFirst = TRUE;
12042 blackPlaysFirst = FALSE;
12043 startedFromSetupPosition = TRUE;
12045 CopyBoard(boards[0], initial_position);
12046 if (blackPlaysFirst) {
12047 currentMove = forwardMostMove = backwardMostMove = 1;
12048 CopyBoard(boards[1], initial_position);
12049 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12050 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12051 timeRemaining[0][1] = whiteTimeRemaining;
12052 timeRemaining[1][1] = blackTimeRemaining;
12053 if (commentList[0] != NULL) {
12054 commentList[1] = commentList[0];
12055 commentList[0] = NULL;
12058 currentMove = forwardMostMove = backwardMostMove = 0;
12061 yyboardindex = forwardMostMove;
12062 cm = (ChessMove) Myylex();
12065 if (first.pr == NoProc) {
12066 StartChessProgram(&first);
12068 InitChessProgram(&first, FALSE);
12069 SendToProgram("force\n", &first);
12070 if (startedFromSetupPosition) {
12071 SendBoard(&first, forwardMostMove);
12072 if (appData.debugMode) {
12073 fprintf(debugFP, "Load Game\n");
12075 DisplayBothClocks();
12078 /* [HGM] server: flag to write setup moves in broadcast file as one */
12079 loadFlag = appData.suppressLoadMoves;
12081 while (cm == Comment) {
12083 if (appData.debugMode)
12084 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12086 AppendComment(currentMove, p, FALSE);
12087 yyboardindex = forwardMostMove;
12088 cm = (ChessMove) Myylex();
12091 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12092 cm == WhiteWins || cm == BlackWins ||
12093 cm == GameIsDrawn || cm == GameUnfinished) {
12094 DisplayMessage("", _("No moves in game"));
12095 if (cmailMsgLoaded) {
12096 if (appData.debugMode)
12097 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12101 DrawPosition(FALSE, boards[currentMove]);
12102 DisplayBothClocks();
12103 gameMode = EditGame;
12110 // [HGM] PV info: routine tests if comment empty
12111 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12112 DisplayComment(currentMove - 1, commentList[currentMove]);
12114 if (!matchMode && appData.timeDelay != 0)
12115 DrawPosition(FALSE, boards[currentMove]);
12117 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12118 programStats.ok_to_send = 1;
12121 /* if the first token after the PGN tags is a move
12122 * and not move number 1, retrieve it from the parser
12124 if (cm != MoveNumberOne)
12125 LoadGameOneMove(cm);
12127 /* load the remaining moves from the file */
12128 while (LoadGameOneMove(EndOfFile)) {
12129 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12130 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12133 /* rewind to the start of the game */
12134 currentMove = backwardMostMove;
12136 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12138 if (oldGameMode == AnalyzeFile ||
12139 oldGameMode == AnalyzeMode) {
12140 AnalyzeFileEvent();
12143 if (!matchMode && pos >= 0) {
12144 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12146 if (matchMode || appData.timeDelay == 0) {
12148 } else if (appData.timeDelay > 0) {
12149 AutoPlayGameLoop();
12152 if (appData.debugMode)
12153 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12155 loadFlag = 0; /* [HGM] true game starts */
12159 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12161 ReloadPosition (int offset)
12163 int positionNumber = lastLoadPositionNumber + offset;
12164 if (lastLoadPositionFP == NULL) {
12165 DisplayError(_("No position has been loaded yet"), 0);
12168 if (positionNumber <= 0) {
12169 DisplayError(_("Can't back up any further"), 0);
12172 return LoadPosition(lastLoadPositionFP, positionNumber,
12173 lastLoadPositionTitle);
12176 /* Load the nth position from the given file */
12178 LoadPositionFromFile (char *filename, int n, char *title)
12183 if (strcmp(filename, "-") == 0) {
12184 return LoadPosition(stdin, n, "stdin");
12186 f = fopen(filename, "rb");
12188 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12189 DisplayError(buf, errno);
12192 return LoadPosition(f, n, title);
12197 /* Load the nth position from the given open file, and close it */
12199 LoadPosition (FILE *f, int positionNumber, char *title)
12201 char *p, line[MSG_SIZ];
12202 Board initial_position;
12203 int i, j, fenMode, pn;
12205 if (gameMode == Training )
12206 SetTrainingModeOff();
12208 if (gameMode != BeginningOfGame) {
12209 Reset(FALSE, TRUE);
12211 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12212 fclose(lastLoadPositionFP);
12214 if (positionNumber == 0) positionNumber = 1;
12215 lastLoadPositionFP = f;
12216 lastLoadPositionNumber = positionNumber;
12217 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12218 if (first.pr == NoProc && !appData.noChessProgram) {
12219 StartChessProgram(&first);
12220 InitChessProgram(&first, FALSE);
12222 pn = positionNumber;
12223 if (positionNumber < 0) {
12224 /* Negative position number means to seek to that byte offset */
12225 if (fseek(f, -positionNumber, 0) == -1) {
12226 DisplayError(_("Can't seek on position file"), 0);
12231 if (fseek(f, 0, 0) == -1) {
12232 if (f == lastLoadPositionFP ?
12233 positionNumber == lastLoadPositionNumber + 1 :
12234 positionNumber == 1) {
12237 DisplayError(_("Can't seek on position file"), 0);
12242 /* See if this file is FEN or old-style xboard */
12243 if (fgets(line, MSG_SIZ, f) == NULL) {
12244 DisplayError(_("Position not found in file"), 0);
12247 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12248 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12251 if (fenMode || line[0] == '#') pn--;
12253 /* skip positions before number pn */
12254 if (fgets(line, MSG_SIZ, f) == NULL) {
12256 DisplayError(_("Position not found in file"), 0);
12259 if (fenMode || line[0] == '#') pn--;
12264 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12265 DisplayError(_("Bad FEN position in file"), 0);
12269 (void) fgets(line, MSG_SIZ, f);
12270 (void) fgets(line, MSG_SIZ, f);
12272 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12273 (void) fgets(line, MSG_SIZ, f);
12274 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12277 initial_position[i][j++] = CharToPiece(*p);
12281 blackPlaysFirst = FALSE;
12283 (void) fgets(line, MSG_SIZ, f);
12284 if (strncmp(line, "black", strlen("black"))==0)
12285 blackPlaysFirst = TRUE;
12288 startedFromSetupPosition = TRUE;
12290 CopyBoard(boards[0], initial_position);
12291 if (blackPlaysFirst) {
12292 currentMove = forwardMostMove = backwardMostMove = 1;
12293 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12294 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12295 CopyBoard(boards[1], initial_position);
12296 DisplayMessage("", _("Black to play"));
12298 currentMove = forwardMostMove = backwardMostMove = 0;
12299 DisplayMessage("", _("White to play"));
12301 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12302 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12303 SendToProgram("force\n", &first);
12304 SendBoard(&first, forwardMostMove);
12306 if (appData.debugMode) {
12308 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12309 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12310 fprintf(debugFP, "Load Position\n");
12313 if (positionNumber > 1) {
12314 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12315 DisplayTitle(line);
12317 DisplayTitle(title);
12319 gameMode = EditGame;
12322 timeRemaining[0][1] = whiteTimeRemaining;
12323 timeRemaining[1][1] = blackTimeRemaining;
12324 DrawPosition(FALSE, boards[currentMove]);
12331 CopyPlayerNameIntoFileName (char **dest, char *src)
12333 while (*src != NULLCHAR && *src != ',') {
12338 *(*dest)++ = *src++;
12344 DefaultFileName (char *ext)
12346 static char def[MSG_SIZ];
12349 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12351 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12353 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12355 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12362 /* Save the current game to the given file */
12364 SaveGameToFile (char *filename, int append)
12368 int result, i, t,tot=0;
12370 if (strcmp(filename, "-") == 0) {
12371 return SaveGame(stdout, 0, NULL);
12373 for(i=0; i<10; i++) { // upto 10 tries
12374 f = fopen(filename, append ? "a" : "w");
12375 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12376 if(f || errno != 13) break;
12377 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12381 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12382 DisplayError(buf, errno);
12385 safeStrCpy(buf, lastMsg, MSG_SIZ);
12386 DisplayMessage(_("Waiting for access to save file"), "");
12387 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12388 DisplayMessage(_("Saving game"), "");
12389 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12390 result = SaveGame(f, 0, NULL);
12391 DisplayMessage(buf, "");
12398 SavePart (char *str)
12400 static char buf[MSG_SIZ];
12403 p = strchr(str, ' ');
12404 if (p == NULL) return str;
12405 strncpy(buf, str, p - str);
12406 buf[p - str] = NULLCHAR;
12410 #define PGN_MAX_LINE 75
12412 #define PGN_SIDE_WHITE 0
12413 #define PGN_SIDE_BLACK 1
12416 FindFirstMoveOutOfBook (int side)
12420 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12421 int index = backwardMostMove;
12422 int has_book_hit = 0;
12424 if( (index % 2) != side ) {
12428 while( index < forwardMostMove ) {
12429 /* Check to see if engine is in book */
12430 int depth = pvInfoList[index].depth;
12431 int score = pvInfoList[index].score;
12437 else if( score == 0 && depth == 63 ) {
12438 in_book = 1; /* Zappa */
12440 else if( score == 2 && depth == 99 ) {
12441 in_book = 1; /* Abrok */
12444 has_book_hit += in_book;
12460 GetOutOfBookInfo (char * buf)
12464 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12466 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12467 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12471 if( oob[0] >= 0 || oob[1] >= 0 ) {
12472 for( i=0; i<2; i++ ) {
12476 if( i > 0 && oob[0] >= 0 ) {
12477 strcat( buf, " " );
12480 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12481 sprintf( buf+strlen(buf), "%s%.2f",
12482 pvInfoList[idx].score >= 0 ? "+" : "",
12483 pvInfoList[idx].score / 100.0 );
12489 /* Save game in PGN style and close the file */
12491 SaveGamePGN (FILE *f)
12493 int i, offset, linelen, newblock;
12497 int movelen, numlen, blank;
12498 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12500 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12502 tm = time((time_t *) NULL);
12504 PrintPGNTags(f, &gameInfo);
12506 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12508 if (backwardMostMove > 0 || startedFromSetupPosition) {
12509 char *fen = PositionToFEN(backwardMostMove, NULL);
12510 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12511 fprintf(f, "\n{--------------\n");
12512 PrintPosition(f, backwardMostMove);
12513 fprintf(f, "--------------}\n");
12517 /* [AS] Out of book annotation */
12518 if( appData.saveOutOfBookInfo ) {
12521 GetOutOfBookInfo( buf );
12523 if( buf[0] != '\0' ) {
12524 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12531 i = backwardMostMove;
12535 while (i < forwardMostMove) {
12536 /* Print comments preceding this move */
12537 if (commentList[i] != NULL) {
12538 if (linelen > 0) fprintf(f, "\n");
12539 fprintf(f, "%s", commentList[i]);
12544 /* Format move number */
12546 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12549 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12551 numtext[0] = NULLCHAR;
12553 numlen = strlen(numtext);
12556 /* Print move number */
12557 blank = linelen > 0 && numlen > 0;
12558 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12567 fprintf(f, "%s", numtext);
12571 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12572 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12575 blank = linelen > 0 && movelen > 0;
12576 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12585 fprintf(f, "%s", move_buffer);
12586 linelen += movelen;
12588 /* [AS] Add PV info if present */
12589 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12590 /* [HGM] add time */
12591 char buf[MSG_SIZ]; int seconds;
12593 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12599 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12602 seconds = (seconds + 4)/10; // round to full seconds
12604 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12606 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12609 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12610 pvInfoList[i].score >= 0 ? "+" : "",
12611 pvInfoList[i].score / 100.0,
12612 pvInfoList[i].depth,
12615 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12617 /* Print score/depth */
12618 blank = linelen > 0 && movelen > 0;
12619 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12628 fprintf(f, "%s", move_buffer);
12629 linelen += movelen;
12635 /* Start a new line */
12636 if (linelen > 0) fprintf(f, "\n");
12638 /* Print comments after last move */
12639 if (commentList[i] != NULL) {
12640 fprintf(f, "%s\n", commentList[i]);
12644 if (gameInfo.resultDetails != NULL &&
12645 gameInfo.resultDetails[0] != NULLCHAR) {
12646 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12647 PGNResult(gameInfo.result));
12649 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12653 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12657 /* Save game in old style and close the file */
12659 SaveGameOldStyle (FILE *f)
12664 tm = time((time_t *) NULL);
12666 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12669 if (backwardMostMove > 0 || startedFromSetupPosition) {
12670 fprintf(f, "\n[--------------\n");
12671 PrintPosition(f, backwardMostMove);
12672 fprintf(f, "--------------]\n");
12677 i = backwardMostMove;
12678 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12680 while (i < forwardMostMove) {
12681 if (commentList[i] != NULL) {
12682 fprintf(f, "[%s]\n", commentList[i]);
12685 if ((i % 2) == 1) {
12686 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12689 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12691 if (commentList[i] != NULL) {
12695 if (i >= forwardMostMove) {
12699 fprintf(f, "%s\n", parseList[i]);
12704 if (commentList[i] != NULL) {
12705 fprintf(f, "[%s]\n", commentList[i]);
12708 /* This isn't really the old style, but it's close enough */
12709 if (gameInfo.resultDetails != NULL &&
12710 gameInfo.resultDetails[0] != NULLCHAR) {
12711 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12712 gameInfo.resultDetails);
12714 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12721 /* Save the current game to open file f and close the file */
12723 SaveGame (FILE *f, int dummy, char *dummy2)
12725 if (gameMode == EditPosition) EditPositionDone(TRUE);
12726 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12727 if (appData.oldSaveStyle)
12728 return SaveGameOldStyle(f);
12730 return SaveGamePGN(f);
12733 /* Save the current position to the given file */
12735 SavePositionToFile (char *filename)
12740 if (strcmp(filename, "-") == 0) {
12741 return SavePosition(stdout, 0, NULL);
12743 f = fopen(filename, "a");
12745 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12746 DisplayError(buf, errno);
12749 safeStrCpy(buf, lastMsg, MSG_SIZ);
12750 DisplayMessage(_("Waiting for access to save file"), "");
12751 flock(fileno(f), LOCK_EX); // [HGM] lock
12752 DisplayMessage(_("Saving position"), "");
12753 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12754 SavePosition(f, 0, NULL);
12755 DisplayMessage(buf, "");
12761 /* Save the current position to the given open file and close the file */
12763 SavePosition (FILE *f, int dummy, char *dummy2)
12768 if (gameMode == EditPosition) EditPositionDone(TRUE);
12769 if (appData.oldSaveStyle) {
12770 tm = time((time_t *) NULL);
12772 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12774 fprintf(f, "[--------------\n");
12775 PrintPosition(f, currentMove);
12776 fprintf(f, "--------------]\n");
12778 fen = PositionToFEN(currentMove, NULL);
12779 fprintf(f, "%s\n", fen);
12787 ReloadCmailMsgEvent (int unregister)
12790 static char *inFilename = NULL;
12791 static char *outFilename;
12793 struct stat inbuf, outbuf;
12796 /* Any registered moves are unregistered if unregister is set, */
12797 /* i.e. invoked by the signal handler */
12799 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12800 cmailMoveRegistered[i] = FALSE;
12801 if (cmailCommentList[i] != NULL) {
12802 free(cmailCommentList[i]);
12803 cmailCommentList[i] = NULL;
12806 nCmailMovesRegistered = 0;
12809 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12810 cmailResult[i] = CMAIL_NOT_RESULT;
12814 if (inFilename == NULL) {
12815 /* Because the filenames are static they only get malloced once */
12816 /* and they never get freed */
12817 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12818 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12820 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12821 sprintf(outFilename, "%s.out", appData.cmailGameName);
12824 status = stat(outFilename, &outbuf);
12826 cmailMailedMove = FALSE;
12828 status = stat(inFilename, &inbuf);
12829 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12832 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12833 counts the games, notes how each one terminated, etc.
12835 It would be nice to remove this kludge and instead gather all
12836 the information while building the game list. (And to keep it
12837 in the game list nodes instead of having a bunch of fixed-size
12838 parallel arrays.) Note this will require getting each game's
12839 termination from the PGN tags, as the game list builder does
12840 not process the game moves. --mann
12842 cmailMsgLoaded = TRUE;
12843 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12845 /* Load first game in the file or popup game menu */
12846 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12848 #endif /* !WIN32 */
12856 char string[MSG_SIZ];
12858 if ( cmailMailedMove
12859 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12860 return TRUE; /* Allow free viewing */
12863 /* Unregister move to ensure that we don't leave RegisterMove */
12864 /* with the move registered when the conditions for registering no */
12866 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12867 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12868 nCmailMovesRegistered --;
12870 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12872 free(cmailCommentList[lastLoadGameNumber - 1]);
12873 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12877 if (cmailOldMove == -1) {
12878 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12882 if (currentMove > cmailOldMove + 1) {
12883 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12887 if (currentMove < cmailOldMove) {
12888 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12892 if (forwardMostMove > currentMove) {
12893 /* Silently truncate extra moves */
12897 if ( (currentMove == cmailOldMove + 1)
12898 || ( (currentMove == cmailOldMove)
12899 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12900 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12901 if (gameInfo.result != GameUnfinished) {
12902 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12905 if (commentList[currentMove] != NULL) {
12906 cmailCommentList[lastLoadGameNumber - 1]
12907 = StrSave(commentList[currentMove]);
12909 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12911 if (appData.debugMode)
12912 fprintf(debugFP, "Saving %s for game %d\n",
12913 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12915 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12917 f = fopen(string, "w");
12918 if (appData.oldSaveStyle) {
12919 SaveGameOldStyle(f); /* also closes the file */
12921 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12922 f = fopen(string, "w");
12923 SavePosition(f, 0, NULL); /* also closes the file */
12925 fprintf(f, "{--------------\n");
12926 PrintPosition(f, currentMove);
12927 fprintf(f, "--------------}\n\n");
12929 SaveGame(f, 0, NULL); /* also closes the file*/
12932 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12933 nCmailMovesRegistered ++;
12934 } else if (nCmailGames == 1) {
12935 DisplayError(_("You have not made a move yet"), 0);
12946 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12947 FILE *commandOutput;
12948 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12949 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12955 if (! cmailMsgLoaded) {
12956 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12960 if (nCmailGames == nCmailResults) {
12961 DisplayError(_("No unfinished games"), 0);
12965 #if CMAIL_PROHIBIT_REMAIL
12966 if (cmailMailedMove) {
12967 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);
12968 DisplayError(msg, 0);
12973 if (! (cmailMailedMove || RegisterMove())) return;
12975 if ( cmailMailedMove
12976 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12977 snprintf(string, MSG_SIZ, partCommandString,
12978 appData.debugMode ? " -v" : "", appData.cmailGameName);
12979 commandOutput = popen(string, "r");
12981 if (commandOutput == NULL) {
12982 DisplayError(_("Failed to invoke cmail"), 0);
12984 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12985 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12987 if (nBuffers > 1) {
12988 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12989 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12990 nBytes = MSG_SIZ - 1;
12992 (void) memcpy(msg, buffer, nBytes);
12994 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12996 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12997 cmailMailedMove = TRUE; /* Prevent >1 moves */
13000 for (i = 0; i < nCmailGames; i ++) {
13001 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13006 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13008 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13010 appData.cmailGameName,
13012 LoadGameFromFile(buffer, 1, buffer, FALSE);
13013 cmailMsgLoaded = FALSE;
13017 DisplayInformation(msg);
13018 pclose(commandOutput);
13021 if ((*cmailMsg) != '\0') {
13022 DisplayInformation(cmailMsg);
13027 #endif /* !WIN32 */
13036 int prependComma = 0;
13038 char string[MSG_SIZ]; /* Space for game-list */
13041 if (!cmailMsgLoaded) return "";
13043 if (cmailMailedMove) {
13044 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13046 /* Create a list of games left */
13047 snprintf(string, MSG_SIZ, "[");
13048 for (i = 0; i < nCmailGames; i ++) {
13049 if (! ( cmailMoveRegistered[i]
13050 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13051 if (prependComma) {
13052 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13054 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13058 strcat(string, number);
13061 strcat(string, "]");
13063 if (nCmailMovesRegistered + nCmailResults == 0) {
13064 switch (nCmailGames) {
13066 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13070 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13074 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13079 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13081 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13086 if (nCmailResults == nCmailGames) {
13087 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13089 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13094 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13106 if (gameMode == Training)
13107 SetTrainingModeOff();
13110 cmailMsgLoaded = FALSE;
13111 if (appData.icsActive) {
13112 SendToICS(ics_prefix);
13113 SendToICS("refresh\n");
13118 ExitEvent (int status)
13122 /* Give up on clean exit */
13126 /* Keep trying for clean exit */
13130 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13132 if (telnetISR != NULL) {
13133 RemoveInputSource(telnetISR);
13135 if (icsPR != NoProc) {
13136 DestroyChildProcess(icsPR, TRUE);
13139 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13140 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13142 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13143 /* make sure this other one finishes before killing it! */
13144 if(endingGame) { int count = 0;
13145 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13146 while(endingGame && count++ < 10) DoSleep(1);
13147 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13150 /* Kill off chess programs */
13151 if (first.pr != NoProc) {
13154 DoSleep( appData.delayBeforeQuit );
13155 SendToProgram("quit\n", &first);
13156 DoSleep( appData.delayAfterQuit );
13157 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13159 if (second.pr != NoProc) {
13160 DoSleep( appData.delayBeforeQuit );
13161 SendToProgram("quit\n", &second);
13162 DoSleep( appData.delayAfterQuit );
13163 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13165 if (first.isr != NULL) {
13166 RemoveInputSource(first.isr);
13168 if (second.isr != NULL) {
13169 RemoveInputSource(second.isr);
13172 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13173 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13175 ShutDownFrontEnd();
13182 if (appData.debugMode)
13183 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13187 if (gameMode == MachinePlaysWhite ||
13188 gameMode == MachinePlaysBlack) {
13191 DisplayBothClocks();
13193 if (gameMode == PlayFromGameFile) {
13194 if (appData.timeDelay >= 0)
13195 AutoPlayGameLoop();
13196 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13197 Reset(FALSE, TRUE);
13198 SendToICS(ics_prefix);
13199 SendToICS("refresh\n");
13200 } else if (currentMove < forwardMostMove) {
13201 ForwardInner(forwardMostMove);
13203 pauseExamInvalid = FALSE;
13205 switch (gameMode) {
13209 pauseExamForwardMostMove = forwardMostMove;
13210 pauseExamInvalid = FALSE;
13213 case IcsPlayingWhite:
13214 case IcsPlayingBlack:
13218 case PlayFromGameFile:
13219 (void) StopLoadGameTimer();
13223 case BeginningOfGame:
13224 if (appData.icsActive) return;
13225 /* else fall through */
13226 case MachinePlaysWhite:
13227 case MachinePlaysBlack:
13228 case TwoMachinesPlay:
13229 if (forwardMostMove == 0)
13230 return; /* don't pause if no one has moved */
13231 if ((gameMode == MachinePlaysWhite &&
13232 !WhiteOnMove(forwardMostMove)) ||
13233 (gameMode == MachinePlaysBlack &&
13234 WhiteOnMove(forwardMostMove))) {
13245 EditCommentEvent ()
13247 char title[MSG_SIZ];
13249 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13250 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13252 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13253 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13254 parseList[currentMove - 1]);
13257 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13264 char *tags = PGNTags(&gameInfo);
13266 EditTagsPopUp(tags, NULL);
13271 AnalyzeModeEvent ()
13273 if (appData.noChessProgram || gameMode == AnalyzeMode)
13276 if (gameMode != AnalyzeFile) {
13277 if (!appData.icsEngineAnalyze) {
13279 if (gameMode != EditGame) return;
13281 ResurrectChessProgram();
13282 SendToProgram("analyze\n", &first);
13283 first.analyzing = TRUE;
13284 /*first.maybeThinking = TRUE;*/
13285 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13286 EngineOutputPopUp();
13288 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13293 StartAnalysisClock();
13294 GetTimeMark(&lastNodeCountTime);
13299 AnalyzeFileEvent ()
13301 if (appData.noChessProgram || gameMode == AnalyzeFile)
13304 if (gameMode != AnalyzeMode) {
13306 if (gameMode != EditGame) return;
13307 ResurrectChessProgram();
13308 SendToProgram("analyze\n", &first);
13309 first.analyzing = TRUE;
13310 /*first.maybeThinking = TRUE;*/
13311 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13312 EngineOutputPopUp();
13314 gameMode = AnalyzeFile;
13319 StartAnalysisClock();
13320 GetTimeMark(&lastNodeCountTime);
13322 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13326 MachineWhiteEvent ()
13329 char *bookHit = NULL;
13331 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13335 if (gameMode == PlayFromGameFile ||
13336 gameMode == TwoMachinesPlay ||
13337 gameMode == Training ||
13338 gameMode == AnalyzeMode ||
13339 gameMode == EndOfGame)
13342 if (gameMode == EditPosition)
13343 EditPositionDone(TRUE);
13345 if (!WhiteOnMove(currentMove)) {
13346 DisplayError(_("It is not White's turn"), 0);
13350 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13353 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13354 gameMode == AnalyzeFile)
13357 ResurrectChessProgram(); /* in case it isn't running */
13358 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13359 gameMode = MachinePlaysWhite;
13362 gameMode = MachinePlaysWhite;
13366 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13368 if (first.sendName) {
13369 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13370 SendToProgram(buf, &first);
13372 if (first.sendTime) {
13373 if (first.useColors) {
13374 SendToProgram("black\n", &first); /*gnu kludge*/
13376 SendTimeRemaining(&first, TRUE);
13378 if (first.useColors) {
13379 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13381 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13382 SetMachineThinkingEnables();
13383 first.maybeThinking = TRUE;
13387 if (appData.autoFlipView && !flipView) {
13388 flipView = !flipView;
13389 DrawPosition(FALSE, NULL);
13390 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13393 if(bookHit) { // [HGM] book: simulate book reply
13394 static char bookMove[MSG_SIZ]; // a bit generous?
13396 programStats.nodes = programStats.depth = programStats.time =
13397 programStats.score = programStats.got_only_move = 0;
13398 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13400 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13401 strcat(bookMove, bookHit);
13402 HandleMachineMove(bookMove, &first);
13407 MachineBlackEvent ()
13410 char *bookHit = NULL;
13412 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13416 if (gameMode == PlayFromGameFile ||
13417 gameMode == TwoMachinesPlay ||
13418 gameMode == Training ||
13419 gameMode == AnalyzeMode ||
13420 gameMode == EndOfGame)
13423 if (gameMode == EditPosition)
13424 EditPositionDone(TRUE);
13426 if (WhiteOnMove(currentMove)) {
13427 DisplayError(_("It is not Black's turn"), 0);
13431 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13434 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13435 gameMode == AnalyzeFile)
13438 ResurrectChessProgram(); /* in case it isn't running */
13439 gameMode = MachinePlaysBlack;
13443 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13445 if (first.sendName) {
13446 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13447 SendToProgram(buf, &first);
13449 if (first.sendTime) {
13450 if (first.useColors) {
13451 SendToProgram("white\n", &first); /*gnu kludge*/
13453 SendTimeRemaining(&first, FALSE);
13455 if (first.useColors) {
13456 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13458 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13459 SetMachineThinkingEnables();
13460 first.maybeThinking = TRUE;
13463 if (appData.autoFlipView && flipView) {
13464 flipView = !flipView;
13465 DrawPosition(FALSE, NULL);
13466 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13468 if(bookHit) { // [HGM] book: simulate book reply
13469 static char bookMove[MSG_SIZ]; // a bit generous?
13471 programStats.nodes = programStats.depth = programStats.time =
13472 programStats.score = programStats.got_only_move = 0;
13473 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13475 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13476 strcat(bookMove, bookHit);
13477 HandleMachineMove(bookMove, &first);
13483 DisplayTwoMachinesTitle ()
13486 if (appData.matchGames > 0) {
13487 if(appData.tourneyFile[0]) {
13488 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13489 gameInfo.white, _("vs."), gameInfo.black,
13490 nextGame+1, appData.matchGames+1,
13491 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13493 if (first.twoMachinesColor[0] == 'w') {
13494 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13495 gameInfo.white, _("vs."), gameInfo.black,
13496 first.matchWins, second.matchWins,
13497 matchGame - 1 - (first.matchWins + second.matchWins));
13499 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13500 gameInfo.white, _("vs."), gameInfo.black,
13501 second.matchWins, first.matchWins,
13502 matchGame - 1 - (first.matchWins + second.matchWins));
13505 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13511 SettingsMenuIfReady ()
13513 if (second.lastPing != second.lastPong) {
13514 DisplayMessage("", _("Waiting for second chess program"));
13515 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13519 DisplayMessage("", "");
13520 SettingsPopUp(&second);
13524 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13527 if (cps->pr == NoProc) {
13528 StartChessProgram(cps);
13529 if (cps->protocolVersion == 1) {
13532 /* kludge: allow timeout for initial "feature" command */
13534 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13535 DisplayMessage("", buf);
13536 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13544 TwoMachinesEvent P((void))
13548 ChessProgramState *onmove;
13549 char *bookHit = NULL;
13550 static int stalling = 0;
13554 if (appData.noChessProgram) return;
13556 switch (gameMode) {
13557 case TwoMachinesPlay:
13559 case MachinePlaysWhite:
13560 case MachinePlaysBlack:
13561 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13562 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13566 case BeginningOfGame:
13567 case PlayFromGameFile:
13570 if (gameMode != EditGame) return;
13573 EditPositionDone(TRUE);
13584 // forwardMostMove = currentMove;
13585 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13587 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13589 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13590 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13591 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13595 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13596 SendToProgram("force\n", &second);
13598 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13601 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13602 if(appData.matchPause>10000 || appData.matchPause<10)
13603 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13604 wait = SubtractTimeMarks(&now, &pauseStart);
13605 if(wait < appData.matchPause) {
13606 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13609 // we are now committed to starting the game
13611 DisplayMessage("", "");
13612 if (startedFromSetupPosition) {
13613 SendBoard(&second, backwardMostMove);
13614 if (appData.debugMode) {
13615 fprintf(debugFP, "Two Machines\n");
13618 for (i = backwardMostMove; i < forwardMostMove; i++) {
13619 SendMoveToProgram(i, &second);
13622 gameMode = TwoMachinesPlay;
13624 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13626 DisplayTwoMachinesTitle();
13628 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13633 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13634 SendToProgram(first.computerString, &first);
13635 if (first.sendName) {
13636 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13637 SendToProgram(buf, &first);
13639 SendToProgram(second.computerString, &second);
13640 if (second.sendName) {
13641 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13642 SendToProgram(buf, &second);
13646 if (!first.sendTime || !second.sendTime) {
13647 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13648 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13650 if (onmove->sendTime) {
13651 if (onmove->useColors) {
13652 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13654 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13656 if (onmove->useColors) {
13657 SendToProgram(onmove->twoMachinesColor, onmove);
13659 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13660 // SendToProgram("go\n", onmove);
13661 onmove->maybeThinking = TRUE;
13662 SetMachineThinkingEnables();
13666 if(bookHit) { // [HGM] book: simulate book reply
13667 static char bookMove[MSG_SIZ]; // a bit generous?
13669 programStats.nodes = programStats.depth = programStats.time =
13670 programStats.score = programStats.got_only_move = 0;
13671 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13673 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13674 strcat(bookMove, bookHit);
13675 savedMessage = bookMove; // args for deferred call
13676 savedState = onmove;
13677 ScheduleDelayedEvent(DeferredBookMove, 1);
13684 if (gameMode == Training) {
13685 SetTrainingModeOff();
13686 gameMode = PlayFromGameFile;
13687 DisplayMessage("", _("Training mode off"));
13689 gameMode = Training;
13690 animateTraining = appData.animate;
13692 /* make sure we are not already at the end of the game */
13693 if (currentMove < forwardMostMove) {
13694 SetTrainingModeOn();
13695 DisplayMessage("", _("Training mode on"));
13697 gameMode = PlayFromGameFile;
13698 DisplayError(_("Already at end of game"), 0);
13707 if (!appData.icsActive) return;
13708 switch (gameMode) {
13709 case IcsPlayingWhite:
13710 case IcsPlayingBlack:
13713 case BeginningOfGame:
13721 EditPositionDone(TRUE);
13734 gameMode = IcsIdle;
13744 switch (gameMode) {
13746 SetTrainingModeOff();
13748 case MachinePlaysWhite:
13749 case MachinePlaysBlack:
13750 case BeginningOfGame:
13751 SendToProgram("force\n", &first);
13752 SetUserThinkingEnables();
13754 case PlayFromGameFile:
13755 (void) StopLoadGameTimer();
13756 if (gameFileFP != NULL) {
13761 EditPositionDone(TRUE);
13766 SendToProgram("force\n", &first);
13768 case TwoMachinesPlay:
13769 GameEnds(EndOfFile, NULL, GE_PLAYER);
13770 ResurrectChessProgram();
13771 SetUserThinkingEnables();
13774 ResurrectChessProgram();
13776 case IcsPlayingBlack:
13777 case IcsPlayingWhite:
13778 DisplayError(_("Warning: You are still playing a game"), 0);
13781 DisplayError(_("Warning: You are still observing a game"), 0);
13784 DisplayError(_("Warning: You are still examining a game"), 0);
13795 first.offeredDraw = second.offeredDraw = 0;
13797 if (gameMode == PlayFromGameFile) {
13798 whiteTimeRemaining = timeRemaining[0][currentMove];
13799 blackTimeRemaining = timeRemaining[1][currentMove];
13803 if (gameMode == MachinePlaysWhite ||
13804 gameMode == MachinePlaysBlack ||
13805 gameMode == TwoMachinesPlay ||
13806 gameMode == EndOfGame) {
13807 i = forwardMostMove;
13808 while (i > currentMove) {
13809 SendToProgram("undo\n", &first);
13812 if(!adjustedClock) {
13813 whiteTimeRemaining = timeRemaining[0][currentMove];
13814 blackTimeRemaining = timeRemaining[1][currentMove];
13815 DisplayBothClocks();
13817 if (whiteFlag || blackFlag) {
13818 whiteFlag = blackFlag = 0;
13823 gameMode = EditGame;
13830 EditPositionEvent ()
13832 if (gameMode == EditPosition) {
13838 if (gameMode != EditGame) return;
13840 gameMode = EditPosition;
13843 if (currentMove > 0)
13844 CopyBoard(boards[0], boards[currentMove]);
13846 blackPlaysFirst = !WhiteOnMove(currentMove);
13848 currentMove = forwardMostMove = backwardMostMove = 0;
13849 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13851 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13857 /* [DM] icsEngineAnalyze - possible call from other functions */
13858 if (appData.icsEngineAnalyze) {
13859 appData.icsEngineAnalyze = FALSE;
13861 DisplayMessage("",_("Close ICS engine analyze..."));
13863 if (first.analysisSupport && first.analyzing) {
13864 SendToProgram("exit\n", &first);
13865 first.analyzing = FALSE;
13867 thinkOutput[0] = NULLCHAR;
13871 EditPositionDone (Boolean fakeRights)
13873 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13875 startedFromSetupPosition = TRUE;
13876 InitChessProgram(&first, FALSE);
13877 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13878 boards[0][EP_STATUS] = EP_NONE;
13879 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13880 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13881 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13882 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13883 } else boards[0][CASTLING][2] = NoRights;
13884 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13885 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13886 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13887 } else boards[0][CASTLING][5] = NoRights;
13889 SendToProgram("force\n", &first);
13890 if (blackPlaysFirst) {
13891 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13892 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13893 currentMove = forwardMostMove = backwardMostMove = 1;
13894 CopyBoard(boards[1], boards[0]);
13896 currentMove = forwardMostMove = backwardMostMove = 0;
13898 SendBoard(&first, forwardMostMove);
13899 if (appData.debugMode) {
13900 fprintf(debugFP, "EditPosDone\n");
13903 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13904 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13905 gameMode = EditGame;
13907 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13908 ClearHighlights(); /* [AS] */
13911 /* Pause for `ms' milliseconds */
13912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13914 TimeDelay (long ms)
13921 } while (SubtractTimeMarks(&m2, &m1) < ms);
13924 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13926 SendMultiLineToICS (char *buf)
13928 char temp[MSG_SIZ+1], *p;
13935 strncpy(temp, buf, len);
13940 if (*p == '\n' || *p == '\r')
13945 strcat(temp, "\n");
13947 SendToPlayer(temp, strlen(temp));
13951 SetWhiteToPlayEvent ()
13953 if (gameMode == EditPosition) {
13954 blackPlaysFirst = FALSE;
13955 DisplayBothClocks(); /* works because currentMove is 0 */
13956 } else if (gameMode == IcsExamining) {
13957 SendToICS(ics_prefix);
13958 SendToICS("tomove white\n");
13963 SetBlackToPlayEvent ()
13965 if (gameMode == EditPosition) {
13966 blackPlaysFirst = TRUE;
13967 currentMove = 1; /* kludge */
13968 DisplayBothClocks();
13970 } else if (gameMode == IcsExamining) {
13971 SendToICS(ics_prefix);
13972 SendToICS("tomove black\n");
13977 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13980 ChessSquare piece = boards[0][y][x];
13982 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13984 switch (selection) {
13986 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13987 SendToICS(ics_prefix);
13988 SendToICS("bsetup clear\n");
13989 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13990 SendToICS(ics_prefix);
13991 SendToICS("clearboard\n");
13993 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13994 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13995 for (y = 0; y < BOARD_HEIGHT; y++) {
13996 if (gameMode == IcsExamining) {
13997 if (boards[currentMove][y][x] != EmptySquare) {
13998 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14003 boards[0][y][x] = p;
14008 if (gameMode == EditPosition) {
14009 DrawPosition(FALSE, boards[0]);
14014 SetWhiteToPlayEvent();
14018 SetBlackToPlayEvent();
14022 if (gameMode == IcsExamining) {
14023 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14024 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14027 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14028 if(x == BOARD_LEFT-2) {
14029 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14030 boards[0][y][1] = 0;
14032 if(x == BOARD_RGHT+1) {
14033 if(y >= gameInfo.holdingsSize) break;
14034 boards[0][y][BOARD_WIDTH-2] = 0;
14037 boards[0][y][x] = EmptySquare;
14038 DrawPosition(FALSE, boards[0]);
14043 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14044 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14045 selection = (ChessSquare) (PROMOTED piece);
14046 } else if(piece == EmptySquare) selection = WhiteSilver;
14047 else selection = (ChessSquare)((int)piece - 1);
14051 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14052 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14053 selection = (ChessSquare) (DEMOTED piece);
14054 } else if(piece == EmptySquare) selection = BlackSilver;
14055 else selection = (ChessSquare)((int)piece + 1);
14060 if(gameInfo.variant == VariantShatranj ||
14061 gameInfo.variant == VariantXiangqi ||
14062 gameInfo.variant == VariantCourier ||
14063 gameInfo.variant == VariantMakruk )
14064 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14069 if(gameInfo.variant == VariantXiangqi)
14070 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14071 if(gameInfo.variant == VariantKnightmate)
14072 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14075 if (gameMode == IcsExamining) {
14076 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14077 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14078 PieceToChar(selection), AAA + x, ONE + y);
14081 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14083 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14084 n = PieceToNumber(selection - BlackPawn);
14085 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14086 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14087 boards[0][BOARD_HEIGHT-1-n][1]++;
14089 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14090 n = PieceToNumber(selection);
14091 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14092 boards[0][n][BOARD_WIDTH-1] = selection;
14093 boards[0][n][BOARD_WIDTH-2]++;
14096 boards[0][y][x] = selection;
14097 DrawPosition(TRUE, boards[0]);
14099 fromX = fromY = -1;
14107 DropMenuEvent (ChessSquare selection, int x, int y)
14109 ChessMove moveType;
14111 switch (gameMode) {
14112 case IcsPlayingWhite:
14113 case MachinePlaysBlack:
14114 if (!WhiteOnMove(currentMove)) {
14115 DisplayMoveError(_("It is Black's turn"));
14118 moveType = WhiteDrop;
14120 case IcsPlayingBlack:
14121 case MachinePlaysWhite:
14122 if (WhiteOnMove(currentMove)) {
14123 DisplayMoveError(_("It is White's turn"));
14126 moveType = BlackDrop;
14129 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14135 if (moveType == BlackDrop && selection < BlackPawn) {
14136 selection = (ChessSquare) ((int) selection
14137 + (int) BlackPawn - (int) WhitePawn);
14139 if (boards[currentMove][y][x] != EmptySquare) {
14140 DisplayMoveError(_("That square is occupied"));
14144 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14150 /* Accept a pending offer of any kind from opponent */
14152 if (appData.icsActive) {
14153 SendToICS(ics_prefix);
14154 SendToICS("accept\n");
14155 } else if (cmailMsgLoaded) {
14156 if (currentMove == cmailOldMove &&
14157 commentList[cmailOldMove] != NULL &&
14158 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14159 "Black offers a draw" : "White offers a draw")) {
14161 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14162 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14164 DisplayError(_("There is no pending offer on this move"), 0);
14165 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14168 /* Not used for offers from chess program */
14175 /* Decline a pending offer of any kind from opponent */
14177 if (appData.icsActive) {
14178 SendToICS(ics_prefix);
14179 SendToICS("decline\n");
14180 } else if (cmailMsgLoaded) {
14181 if (currentMove == cmailOldMove &&
14182 commentList[cmailOldMove] != NULL &&
14183 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14184 "Black offers a draw" : "White offers a draw")) {
14186 AppendComment(cmailOldMove, "Draw declined", TRUE);
14187 DisplayComment(cmailOldMove - 1, "Draw declined");
14190 DisplayError(_("There is no pending offer on this move"), 0);
14193 /* Not used for offers from chess program */
14200 /* Issue ICS rematch command */
14201 if (appData.icsActive) {
14202 SendToICS(ics_prefix);
14203 SendToICS("rematch\n");
14210 /* Call your opponent's flag (claim a win on time) */
14211 if (appData.icsActive) {
14212 SendToICS(ics_prefix);
14213 SendToICS("flag\n");
14215 switch (gameMode) {
14218 case MachinePlaysWhite:
14221 GameEnds(GameIsDrawn, "Both players ran out of time",
14224 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14226 DisplayError(_("Your opponent is not out of time"), 0);
14229 case MachinePlaysBlack:
14232 GameEnds(GameIsDrawn, "Both players ran out of time",
14235 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14237 DisplayError(_("Your opponent is not out of time"), 0);
14245 ClockClick (int which)
14246 { // [HGM] code moved to back-end from winboard.c
14247 if(which) { // black clock
14248 if (gameMode == EditPosition || gameMode == IcsExamining) {
14249 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14250 SetBlackToPlayEvent();
14251 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14252 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14253 } else if (shiftKey) {
14254 AdjustClock(which, -1);
14255 } else if (gameMode == IcsPlayingWhite ||
14256 gameMode == MachinePlaysBlack) {
14259 } else { // white clock
14260 if (gameMode == EditPosition || gameMode == IcsExamining) {
14261 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14262 SetWhiteToPlayEvent();
14263 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14264 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14265 } else if (shiftKey) {
14266 AdjustClock(which, -1);
14267 } else if (gameMode == IcsPlayingBlack ||
14268 gameMode == MachinePlaysWhite) {
14277 /* Offer draw or accept pending draw offer from opponent */
14279 if (appData.icsActive) {
14280 /* Note: tournament rules require draw offers to be
14281 made after you make your move but before you punch
14282 your clock. Currently ICS doesn't let you do that;
14283 instead, you immediately punch your clock after making
14284 a move, but you can offer a draw at any time. */
14286 SendToICS(ics_prefix);
14287 SendToICS("draw\n");
14288 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14289 } else if (cmailMsgLoaded) {
14290 if (currentMove == cmailOldMove &&
14291 commentList[cmailOldMove] != NULL &&
14292 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14293 "Black offers a draw" : "White offers a draw")) {
14294 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14295 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14296 } else if (currentMove == cmailOldMove + 1) {
14297 char *offer = WhiteOnMove(cmailOldMove) ?
14298 "White offers a draw" : "Black offers a draw";
14299 AppendComment(currentMove, offer, TRUE);
14300 DisplayComment(currentMove - 1, offer);
14301 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14303 DisplayError(_("You must make your move before offering a draw"), 0);
14304 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14306 } else if (first.offeredDraw) {
14307 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14309 if (first.sendDrawOffers) {
14310 SendToProgram("draw\n", &first);
14311 userOfferedDraw = TRUE;
14319 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14321 if (appData.icsActive) {
14322 SendToICS(ics_prefix);
14323 SendToICS("adjourn\n");
14325 /* Currently GNU Chess doesn't offer or accept Adjourns */
14333 /* Offer Abort or accept pending Abort offer from opponent */
14335 if (appData.icsActive) {
14336 SendToICS(ics_prefix);
14337 SendToICS("abort\n");
14339 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14346 /* Resign. You can do this even if it's not your turn. */
14348 if (appData.icsActive) {
14349 SendToICS(ics_prefix);
14350 SendToICS("resign\n");
14352 switch (gameMode) {
14353 case MachinePlaysWhite:
14354 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14356 case MachinePlaysBlack:
14357 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14360 if (cmailMsgLoaded) {
14362 if (WhiteOnMove(cmailOldMove)) {
14363 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14365 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14367 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14378 StopObservingEvent ()
14380 /* Stop observing current games */
14381 SendToICS(ics_prefix);
14382 SendToICS("unobserve\n");
14386 StopExaminingEvent ()
14388 /* Stop observing current game */
14389 SendToICS(ics_prefix);
14390 SendToICS("unexamine\n");
14394 ForwardInner (int target)
14396 int limit; int oldSeekGraphUp = seekGraphUp;
14398 if (appData.debugMode)
14399 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14400 target, currentMove, forwardMostMove);
14402 if (gameMode == EditPosition)
14405 seekGraphUp = FALSE;
14406 MarkTargetSquares(1);
14408 if (gameMode == PlayFromGameFile && !pausing)
14411 if (gameMode == IcsExamining && pausing)
14412 limit = pauseExamForwardMostMove;
14414 limit = forwardMostMove;
14416 if (target > limit) target = limit;
14418 if (target > 0 && moveList[target - 1][0]) {
14419 int fromX, fromY, toX, toY;
14420 toX = moveList[target - 1][2] - AAA;
14421 toY = moveList[target - 1][3] - ONE;
14422 if (moveList[target - 1][1] == '@') {
14423 if (appData.highlightLastMove) {
14424 SetHighlights(-1, -1, toX, toY);
14427 fromX = moveList[target - 1][0] - AAA;
14428 fromY = moveList[target - 1][1] - ONE;
14429 if (target == currentMove + 1) {
14430 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14432 if (appData.highlightLastMove) {
14433 SetHighlights(fromX, fromY, toX, toY);
14437 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14438 gameMode == Training || gameMode == PlayFromGameFile ||
14439 gameMode == AnalyzeFile) {
14440 while (currentMove < target) {
14441 SendMoveToProgram(currentMove++, &first);
14444 currentMove = target;
14447 if (gameMode == EditGame || gameMode == EndOfGame) {
14448 whiteTimeRemaining = timeRemaining[0][currentMove];
14449 blackTimeRemaining = timeRemaining[1][currentMove];
14451 DisplayBothClocks();
14452 DisplayMove(currentMove - 1);
14453 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14454 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14455 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14456 DisplayComment(currentMove - 1, commentList[currentMove]);
14458 ClearMap(); // [HGM] exclude: invalidate map
14465 if (gameMode == IcsExamining && !pausing) {
14466 SendToICS(ics_prefix);
14467 SendToICS("forward\n");
14469 ForwardInner(currentMove + 1);
14476 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14477 /* to optimze, we temporarily turn off analysis mode while we feed
14478 * the remaining moves to the engine. Otherwise we get analysis output
14481 if (first.analysisSupport) {
14482 SendToProgram("exit\nforce\n", &first);
14483 first.analyzing = FALSE;
14487 if (gameMode == IcsExamining && !pausing) {
14488 SendToICS(ics_prefix);
14489 SendToICS("forward 999999\n");
14491 ForwardInner(forwardMostMove);
14494 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14495 /* we have fed all the moves, so reactivate analysis mode */
14496 SendToProgram("analyze\n", &first);
14497 first.analyzing = TRUE;
14498 /*first.maybeThinking = TRUE;*/
14499 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14504 BackwardInner (int target)
14506 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14508 if (appData.debugMode)
14509 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14510 target, currentMove, forwardMostMove);
14512 if (gameMode == EditPosition) return;
14513 seekGraphUp = FALSE;
14514 MarkTargetSquares(1);
14515 if (currentMove <= backwardMostMove) {
14517 DrawPosition(full_redraw, boards[currentMove]);
14520 if (gameMode == PlayFromGameFile && !pausing)
14523 if (moveList[target][0]) {
14524 int fromX, fromY, toX, toY;
14525 toX = moveList[target][2] - AAA;
14526 toY = moveList[target][3] - ONE;
14527 if (moveList[target][1] == '@') {
14528 if (appData.highlightLastMove) {
14529 SetHighlights(-1, -1, toX, toY);
14532 fromX = moveList[target][0] - AAA;
14533 fromY = moveList[target][1] - ONE;
14534 if (target == currentMove - 1) {
14535 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14537 if (appData.highlightLastMove) {
14538 SetHighlights(fromX, fromY, toX, toY);
14542 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14543 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14544 while (currentMove > target) {
14545 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14546 // null move cannot be undone. Reload program with move history before it.
14548 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14549 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14551 SendBoard(&first, i);
14552 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14555 SendToProgram("undo\n", &first);
14559 currentMove = target;
14562 if (gameMode == EditGame || gameMode == EndOfGame) {
14563 whiteTimeRemaining = timeRemaining[0][currentMove];
14564 blackTimeRemaining = timeRemaining[1][currentMove];
14566 DisplayBothClocks();
14567 DisplayMove(currentMove - 1);
14568 DrawPosition(full_redraw, boards[currentMove]);
14569 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14570 // [HGM] PV info: routine tests if comment empty
14571 DisplayComment(currentMove - 1, commentList[currentMove]);
14572 ClearMap(); // [HGM] exclude: invalidate map
14578 if (gameMode == IcsExamining && !pausing) {
14579 SendToICS(ics_prefix);
14580 SendToICS("backward\n");
14582 BackwardInner(currentMove - 1);
14589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14590 /* to optimize, we temporarily turn off analysis mode while we undo
14591 * all the moves. Otherwise we get analysis output after each undo.
14593 if (first.analysisSupport) {
14594 SendToProgram("exit\nforce\n", &first);
14595 first.analyzing = FALSE;
14599 if (gameMode == IcsExamining && !pausing) {
14600 SendToICS(ics_prefix);
14601 SendToICS("backward 999999\n");
14603 BackwardInner(backwardMostMove);
14606 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14607 /* we have fed all the moves, so reactivate analysis mode */
14608 SendToProgram("analyze\n", &first);
14609 first.analyzing = TRUE;
14610 /*first.maybeThinking = TRUE;*/
14611 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14618 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14619 if (to >= forwardMostMove) to = forwardMostMove;
14620 if (to <= backwardMostMove) to = backwardMostMove;
14621 if (to < currentMove) {
14629 RevertEvent (Boolean annotate)
14631 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14634 if (gameMode != IcsExamining) {
14635 DisplayError(_("You are not examining a game"), 0);
14639 DisplayError(_("You can't revert while pausing"), 0);
14642 SendToICS(ics_prefix);
14643 SendToICS("revert\n");
14647 RetractMoveEvent ()
14649 switch (gameMode) {
14650 case MachinePlaysWhite:
14651 case MachinePlaysBlack:
14652 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14653 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14656 if (forwardMostMove < 2) return;
14657 currentMove = forwardMostMove = forwardMostMove - 2;
14658 whiteTimeRemaining = timeRemaining[0][currentMove];
14659 blackTimeRemaining = timeRemaining[1][currentMove];
14660 DisplayBothClocks();
14661 DisplayMove(currentMove - 1);
14662 ClearHighlights();/*!! could figure this out*/
14663 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14664 SendToProgram("remove\n", &first);
14665 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14668 case BeginningOfGame:
14672 case IcsPlayingWhite:
14673 case IcsPlayingBlack:
14674 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14675 SendToICS(ics_prefix);
14676 SendToICS("takeback 2\n");
14678 SendToICS(ics_prefix);
14679 SendToICS("takeback 1\n");
14688 ChessProgramState *cps;
14690 switch (gameMode) {
14691 case MachinePlaysWhite:
14692 if (!WhiteOnMove(forwardMostMove)) {
14693 DisplayError(_("It is your turn"), 0);
14698 case MachinePlaysBlack:
14699 if (WhiteOnMove(forwardMostMove)) {
14700 DisplayError(_("It is your turn"), 0);
14705 case TwoMachinesPlay:
14706 if (WhiteOnMove(forwardMostMove) ==
14707 (first.twoMachinesColor[0] == 'w')) {
14713 case BeginningOfGame:
14717 SendToProgram("?\n", cps);
14721 TruncateGameEvent ()
14724 if (gameMode != EditGame) return;
14731 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14732 if (forwardMostMove > currentMove) {
14733 if (gameInfo.resultDetails != NULL) {
14734 free(gameInfo.resultDetails);
14735 gameInfo.resultDetails = NULL;
14736 gameInfo.result = GameUnfinished;
14738 forwardMostMove = currentMove;
14739 HistorySet(parseList, backwardMostMove, forwardMostMove,
14747 if (appData.noChessProgram) return;
14748 switch (gameMode) {
14749 case MachinePlaysWhite:
14750 if (WhiteOnMove(forwardMostMove)) {
14751 DisplayError(_("Wait until your turn"), 0);
14755 case BeginningOfGame:
14756 case MachinePlaysBlack:
14757 if (!WhiteOnMove(forwardMostMove)) {
14758 DisplayError(_("Wait until your turn"), 0);
14763 DisplayError(_("No hint available"), 0);
14766 SendToProgram("hint\n", &first);
14767 hintRequested = TRUE;
14773 if (appData.noChessProgram) return;
14774 switch (gameMode) {
14775 case MachinePlaysWhite:
14776 if (WhiteOnMove(forwardMostMove)) {
14777 DisplayError(_("Wait until your turn"), 0);
14781 case BeginningOfGame:
14782 case MachinePlaysBlack:
14783 if (!WhiteOnMove(forwardMostMove)) {
14784 DisplayError(_("Wait until your turn"), 0);
14789 EditPositionDone(TRUE);
14791 case TwoMachinesPlay:
14796 SendToProgram("bk\n", &first);
14797 bookOutput[0] = NULLCHAR;
14798 bookRequested = TRUE;
14804 char *tags = PGNTags(&gameInfo);
14805 TagsPopUp(tags, CmailMsg());
14809 /* end button procedures */
14812 PrintPosition (FILE *fp, int move)
14816 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14817 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14818 char c = PieceToChar(boards[move][i][j]);
14819 fputc(c == 'x' ? '.' : c, fp);
14820 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14823 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14824 fprintf(fp, "white to play\n");
14826 fprintf(fp, "black to play\n");
14830 PrintOpponents (FILE *fp)
14832 if (gameInfo.white != NULL) {
14833 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14839 /* Find last component of program's own name, using some heuristics */
14841 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14844 int local = (strcmp(host, "localhost") == 0);
14845 while (!local && (p = strchr(prog, ';')) != NULL) {
14847 while (*p == ' ') p++;
14850 if (*prog == '"' || *prog == '\'') {
14851 q = strchr(prog + 1, *prog);
14853 q = strchr(prog, ' ');
14855 if (q == NULL) q = prog + strlen(prog);
14857 while (p >= prog && *p != '/' && *p != '\\') p--;
14859 if(p == prog && *p == '"') p++;
14861 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14862 memcpy(buf, p, q - p);
14863 buf[q - p] = NULLCHAR;
14871 TimeControlTagValue ()
14874 if (!appData.clockMode) {
14875 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14876 } else if (movesPerSession > 0) {
14877 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14878 } else if (timeIncrement == 0) {
14879 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14881 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14883 return StrSave(buf);
14889 /* This routine is used only for certain modes */
14890 VariantClass v = gameInfo.variant;
14891 ChessMove r = GameUnfinished;
14894 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14895 r = gameInfo.result;
14896 p = gameInfo.resultDetails;
14897 gameInfo.resultDetails = NULL;
14899 ClearGameInfo(&gameInfo);
14900 gameInfo.variant = v;
14902 switch (gameMode) {
14903 case MachinePlaysWhite:
14904 gameInfo.event = StrSave( appData.pgnEventHeader );
14905 gameInfo.site = StrSave(HostName());
14906 gameInfo.date = PGNDate();
14907 gameInfo.round = StrSave("-");
14908 gameInfo.white = StrSave(first.tidy);
14909 gameInfo.black = StrSave(UserName());
14910 gameInfo.timeControl = TimeControlTagValue();
14913 case MachinePlaysBlack:
14914 gameInfo.event = StrSave( appData.pgnEventHeader );
14915 gameInfo.site = StrSave(HostName());
14916 gameInfo.date = PGNDate();
14917 gameInfo.round = StrSave("-");
14918 gameInfo.white = StrSave(UserName());
14919 gameInfo.black = StrSave(first.tidy);
14920 gameInfo.timeControl = TimeControlTagValue();
14923 case TwoMachinesPlay:
14924 gameInfo.event = StrSave( appData.pgnEventHeader );
14925 gameInfo.site = StrSave(HostName());
14926 gameInfo.date = PGNDate();
14929 snprintf(buf, MSG_SIZ, "%d", roundNr);
14930 gameInfo.round = StrSave(buf);
14932 gameInfo.round = StrSave("-");
14934 if (first.twoMachinesColor[0] == 'w') {
14935 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14936 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14938 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14939 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14941 gameInfo.timeControl = TimeControlTagValue();
14945 gameInfo.event = StrSave("Edited game");
14946 gameInfo.site = StrSave(HostName());
14947 gameInfo.date = PGNDate();
14948 gameInfo.round = StrSave("-");
14949 gameInfo.white = StrSave("-");
14950 gameInfo.black = StrSave("-");
14951 gameInfo.result = r;
14952 gameInfo.resultDetails = p;
14956 gameInfo.event = StrSave("Edited position");
14957 gameInfo.site = StrSave(HostName());
14958 gameInfo.date = PGNDate();
14959 gameInfo.round = StrSave("-");
14960 gameInfo.white = StrSave("-");
14961 gameInfo.black = StrSave("-");
14964 case IcsPlayingWhite:
14965 case IcsPlayingBlack:
14970 case PlayFromGameFile:
14971 gameInfo.event = StrSave("Game from non-PGN file");
14972 gameInfo.site = StrSave(HostName());
14973 gameInfo.date = PGNDate();
14974 gameInfo.round = StrSave("-");
14975 gameInfo.white = StrSave("?");
14976 gameInfo.black = StrSave("?");
14985 ReplaceComment (int index, char *text)
14991 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14992 pvInfoList[index-1].depth == len &&
14993 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14994 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14995 while (*text == '\n') text++;
14996 len = strlen(text);
14997 while (len > 0 && text[len - 1] == '\n') len--;
14999 if (commentList[index] != NULL)
15000 free(commentList[index]);
15003 commentList[index] = NULL;
15006 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15007 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15008 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15009 commentList[index] = (char *) malloc(len + 2);
15010 strncpy(commentList[index], text, len);
15011 commentList[index][len] = '\n';
15012 commentList[index][len + 1] = NULLCHAR;
15014 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15016 commentList[index] = (char *) malloc(len + 7);
15017 safeStrCpy(commentList[index], "{\n", 3);
15018 safeStrCpy(commentList[index]+2, text, len+1);
15019 commentList[index][len+2] = NULLCHAR;
15020 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15021 strcat(commentList[index], "\n}\n");
15026 CrushCRs (char *text)
15034 if (ch == '\r') continue;
15036 } while (ch != '\0');
15040 AppendComment (int index, char *text, Boolean addBraces)
15041 /* addBraces tells if we should add {} */
15046 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15047 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15050 while (*text == '\n') text++;
15051 len = strlen(text);
15052 while (len > 0 && text[len - 1] == '\n') len--;
15053 text[len] = NULLCHAR;
15055 if (len == 0) return;
15057 if (commentList[index] != NULL) {
15058 Boolean addClosingBrace = addBraces;
15059 old = commentList[index];
15060 oldlen = strlen(old);
15061 while(commentList[index][oldlen-1] == '\n')
15062 commentList[index][--oldlen] = NULLCHAR;
15063 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15064 safeStrCpy(commentList[index], old, oldlen + len + 6);
15066 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15067 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15068 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15069 while (*text == '\n') { text++; len--; }
15070 commentList[index][--oldlen] = NULLCHAR;
15072 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15073 else strcat(commentList[index], "\n");
15074 strcat(commentList[index], text);
15075 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15076 else strcat(commentList[index], "\n");
15078 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15080 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15081 else commentList[index][0] = NULLCHAR;
15082 strcat(commentList[index], text);
15083 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15084 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15089 FindStr (char * text, char * sub_text)
15091 char * result = strstr( text, sub_text );
15093 if( result != NULL ) {
15094 result += strlen( sub_text );
15100 /* [AS] Try to extract PV info from PGN comment */
15101 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15103 GetInfoFromComment (int index, char * text)
15105 char * sep = text, *p;
15107 if( text != NULL && index > 0 ) {
15110 int time = -1, sec = 0, deci;
15111 char * s_eval = FindStr( text, "[%eval " );
15112 char * s_emt = FindStr( text, "[%emt " );
15114 if( s_eval != NULL || s_emt != NULL ) {
15118 if( s_eval != NULL ) {
15119 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15123 if( delim != ']' ) {
15128 if( s_emt != NULL ) {
15133 /* We expect something like: [+|-]nnn.nn/dd */
15136 if(*text != '{') return text; // [HGM] braces: must be normal comment
15138 sep = strchr( text, '/' );
15139 if( sep == NULL || sep < (text+4) ) {
15144 if(p[1] == '(') { // comment starts with PV
15145 p = strchr(p, ')'); // locate end of PV
15146 if(p == NULL || sep < p+5) return text;
15147 // at this point we have something like "{(.*) +0.23/6 ..."
15148 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15149 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15150 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15152 time = -1; sec = -1; deci = -1;
15153 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15154 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15155 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15156 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15160 if( score_lo < 0 || score_lo >= 100 ) {
15164 if(sec >= 0) time = 600*time + 10*sec; else
15165 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15167 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15169 /* [HGM] PV time: now locate end of PV info */
15170 while( *++sep >= '0' && *sep <= '9'); // strip depth
15172 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15174 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15176 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15177 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15188 pvInfoList[index-1].depth = depth;
15189 pvInfoList[index-1].score = score;
15190 pvInfoList[index-1].time = 10*time; // centi-sec
15191 if(*sep == '}') *sep = 0; else *--sep = '{';
15192 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15198 SendToProgram (char *message, ChessProgramState *cps)
15200 int count, outCount, error;
15203 if (cps->pr == NoProc) return;
15206 if (appData.debugMode) {
15209 fprintf(debugFP, "%ld >%-6s: %s",
15210 SubtractTimeMarks(&now, &programStartTime),
15211 cps->which, message);
15213 fprintf(serverFP, "%ld >%-6s: %s",
15214 SubtractTimeMarks(&now, &programStartTime),
15215 cps->which, message), fflush(serverFP);
15218 count = strlen(message);
15219 outCount = OutputToProcess(cps->pr, message, count, &error);
15220 if (outCount < count && !exiting
15221 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15222 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15223 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15224 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15225 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15226 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15227 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15228 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15230 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15231 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15232 gameInfo.result = res;
15234 gameInfo.resultDetails = StrSave(buf);
15236 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15237 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15242 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15246 ChessProgramState *cps = (ChessProgramState *)closure;
15248 if (isr != cps->isr) return; /* Killed intentionally */
15251 RemoveInputSource(cps->isr);
15252 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15253 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15254 _(cps->which), cps->program);
15255 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15256 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15257 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15258 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15259 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15261 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15262 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15263 gameInfo.result = res;
15265 gameInfo.resultDetails = StrSave(buf);
15267 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15268 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15270 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15271 _(cps->which), cps->program);
15272 RemoveInputSource(cps->isr);
15274 /* [AS] Program is misbehaving badly... kill it */
15275 if( count == -2 ) {
15276 DestroyChildProcess( cps->pr, 9 );
15280 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15285 if ((end_str = strchr(message, '\r')) != NULL)
15286 *end_str = NULLCHAR;
15287 if ((end_str = strchr(message, '\n')) != NULL)
15288 *end_str = NULLCHAR;
15290 if (appData.debugMode) {
15291 TimeMark now; int print = 1;
15292 char *quote = ""; char c; int i;
15294 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15295 char start = message[0];
15296 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15297 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15298 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15299 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15300 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15301 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15302 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15303 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15304 sscanf(message, "hint: %c", &c)!=1 &&
15305 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15306 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15307 print = (appData.engineComments >= 2);
15309 message[0] = start; // restore original message
15313 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15314 SubtractTimeMarks(&now, &programStartTime), cps->which,
15318 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15319 SubtractTimeMarks(&now, &programStartTime), cps->which,
15321 message), fflush(serverFP);
15325 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15326 if (appData.icsEngineAnalyze) {
15327 if (strstr(message, "whisper") != NULL ||
15328 strstr(message, "kibitz") != NULL ||
15329 strstr(message, "tellics") != NULL) return;
15332 HandleMachineMove(message, cps);
15337 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15342 if( timeControl_2 > 0 ) {
15343 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15344 tc = timeControl_2;
15347 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15348 inc /= cps->timeOdds;
15349 st /= cps->timeOdds;
15351 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15354 /* Set exact time per move, normally using st command */
15355 if (cps->stKludge) {
15356 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15358 if (seconds == 0) {
15359 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15361 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15364 snprintf(buf, MSG_SIZ, "st %d\n", st);
15367 /* Set conventional or incremental time control, using level command */
15368 if (seconds == 0) {
15369 /* Note old gnuchess bug -- minutes:seconds used to not work.
15370 Fixed in later versions, but still avoid :seconds
15371 when seconds is 0. */
15372 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15374 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15375 seconds, inc/1000.);
15378 SendToProgram(buf, cps);
15380 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15381 /* Orthogonally, limit search to given depth */
15383 if (cps->sdKludge) {
15384 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15386 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15388 SendToProgram(buf, cps);
15391 if(cps->nps >= 0) { /* [HGM] nps */
15392 if(cps->supportsNPS == FALSE)
15393 cps->nps = -1; // don't use if engine explicitly says not supported!
15395 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15396 SendToProgram(buf, cps);
15401 ChessProgramState *
15403 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15405 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15406 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15412 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15414 char message[MSG_SIZ];
15417 /* Note: this routine must be called when the clocks are stopped
15418 or when they have *just* been set or switched; otherwise
15419 it will be off by the time since the current tick started.
15421 if (machineWhite) {
15422 time = whiteTimeRemaining / 10;
15423 otime = blackTimeRemaining / 10;
15425 time = blackTimeRemaining / 10;
15426 otime = whiteTimeRemaining / 10;
15428 /* [HGM] translate opponent's time by time-odds factor */
15429 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15431 if (time <= 0) time = 1;
15432 if (otime <= 0) otime = 1;
15434 snprintf(message, MSG_SIZ, "time %ld\n", time);
15435 SendToProgram(message, cps);
15437 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15438 SendToProgram(message, cps);
15442 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15445 int len = strlen(name);
15448 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15450 sscanf(*p, "%d", &val);
15452 while (**p && **p != ' ')
15454 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15455 SendToProgram(buf, cps);
15462 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15465 int len = strlen(name);
15466 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15468 sscanf(*p, "%d", loc);
15469 while (**p && **p != ' ') (*p)++;
15470 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15471 SendToProgram(buf, cps);
15478 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15481 int len = strlen(name);
15482 if (strncmp((*p), name, len) == 0
15483 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15485 sscanf(*p, "%[^\"]", loc);
15486 while (**p && **p != '\"') (*p)++;
15487 if (**p == '\"') (*p)++;
15488 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15489 SendToProgram(buf, cps);
15496 ParseOption (Option *opt, ChessProgramState *cps)
15497 // [HGM] options: process the string that defines an engine option, and determine
15498 // name, type, default value, and allowed value range
15500 char *p, *q, buf[MSG_SIZ];
15501 int n, min = (-1)<<31, max = 1<<31, def;
15503 if(p = strstr(opt->name, " -spin ")) {
15504 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15505 if(max < min) max = min; // enforce consistency
15506 if(def < min) def = min;
15507 if(def > max) def = max;
15512 } else if((p = strstr(opt->name, " -slider "))) {
15513 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15514 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15515 if(max < min) max = min; // enforce consistency
15516 if(def < min) def = min;
15517 if(def > max) def = max;
15521 opt->type = Spin; // Slider;
15522 } else if((p = strstr(opt->name, " -string "))) {
15523 opt->textValue = p+9;
15524 opt->type = TextBox;
15525 } else if((p = strstr(opt->name, " -file "))) {
15526 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15527 opt->textValue = p+7;
15528 opt->type = FileName; // FileName;
15529 } else if((p = strstr(opt->name, " -path "))) {
15530 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15531 opt->textValue = p+7;
15532 opt->type = PathName; // PathName;
15533 } else if(p = strstr(opt->name, " -check ")) {
15534 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15535 opt->value = (def != 0);
15536 opt->type = CheckBox;
15537 } else if(p = strstr(opt->name, " -combo ")) {
15538 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15539 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15540 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15541 opt->value = n = 0;
15542 while(q = StrStr(q, " /// ")) {
15543 n++; *q = 0; // count choices, and null-terminate each of them
15545 if(*q == '*') { // remember default, which is marked with * prefix
15549 cps->comboList[cps->comboCnt++] = q;
15551 cps->comboList[cps->comboCnt++] = NULL;
15553 opt->type = ComboBox;
15554 } else if(p = strstr(opt->name, " -button")) {
15555 opt->type = Button;
15556 } else if(p = strstr(opt->name, " -save")) {
15557 opt->type = SaveButton;
15558 } else return FALSE;
15559 *p = 0; // terminate option name
15560 // now look if the command-line options define a setting for this engine option.
15561 if(cps->optionSettings && cps->optionSettings[0])
15562 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15563 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15564 snprintf(buf, MSG_SIZ, "option %s", p);
15565 if(p = strstr(buf, ",")) *p = 0;
15566 if(q = strchr(buf, '=')) switch(opt->type) {
15568 for(n=0; n<opt->max; n++)
15569 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15572 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15576 opt->value = atoi(q+1);
15581 SendToProgram(buf, cps);
15587 FeatureDone (ChessProgramState *cps, int val)
15589 DelayedEventCallback cb = GetDelayedEvent();
15590 if ((cb == InitBackEnd3 && cps == &first) ||
15591 (cb == SettingsMenuIfReady && cps == &second) ||
15592 (cb == LoadEngine) ||
15593 (cb == TwoMachinesEventIfReady)) {
15594 CancelDelayedEvent();
15595 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15597 cps->initDone = val;
15600 /* Parse feature command from engine */
15602 ParseFeatures (char *args, ChessProgramState *cps)
15610 while (*p == ' ') p++;
15611 if (*p == NULLCHAR) return;
15613 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15614 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15615 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15616 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15617 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15618 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15619 if (BoolFeature(&p, "reuse", &val, cps)) {
15620 /* Engine can disable reuse, but can't enable it if user said no */
15621 if (!val) cps->reuse = FALSE;
15624 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15625 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15626 if (gameMode == TwoMachinesPlay) {
15627 DisplayTwoMachinesTitle();
15633 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15634 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15635 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15636 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15637 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15638 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15639 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15640 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15641 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15642 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15643 if (IntFeature(&p, "done", &val, cps)) {
15644 FeatureDone(cps, val);
15647 /* Added by Tord: */
15648 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15649 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15650 /* End of additions by Tord */
15652 /* [HGM] added features: */
15653 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15654 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15655 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15656 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15657 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15658 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15659 if (StringFeature(&p, "option", buf, cps)) {
15660 FREE(cps->option[cps->nrOptions].name);
15661 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15662 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15663 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15664 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15665 SendToProgram(buf, cps);
15668 if(cps->nrOptions >= MAX_OPTIONS) {
15670 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15671 DisplayError(buf, 0);
15675 /* End of additions by HGM */
15677 /* unknown feature: complain and skip */
15679 while (*q && *q != '=') q++;
15680 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15681 SendToProgram(buf, cps);
15687 while (*p && *p != '\"') p++;
15688 if (*p == '\"') p++;
15690 while (*p && *p != ' ') p++;
15698 PeriodicUpdatesEvent (int newState)
15700 if (newState == appData.periodicUpdates)
15703 appData.periodicUpdates=newState;
15705 /* Display type changes, so update it now */
15706 // DisplayAnalysis();
15708 /* Get the ball rolling again... */
15710 AnalysisPeriodicEvent(1);
15711 StartAnalysisClock();
15716 PonderNextMoveEvent (int newState)
15718 if (newState == appData.ponderNextMove) return;
15719 if (gameMode == EditPosition) EditPositionDone(TRUE);
15721 SendToProgram("hard\n", &first);
15722 if (gameMode == TwoMachinesPlay) {
15723 SendToProgram("hard\n", &second);
15726 SendToProgram("easy\n", &first);
15727 thinkOutput[0] = NULLCHAR;
15728 if (gameMode == TwoMachinesPlay) {
15729 SendToProgram("easy\n", &second);
15732 appData.ponderNextMove = newState;
15736 NewSettingEvent (int option, int *feature, char *command, int value)
15740 if (gameMode == EditPosition) EditPositionDone(TRUE);
15741 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15742 if(feature == NULL || *feature) SendToProgram(buf, &first);
15743 if (gameMode == TwoMachinesPlay) {
15744 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15749 ShowThinkingEvent ()
15750 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15752 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15753 int newState = appData.showThinking
15754 // [HGM] thinking: other features now need thinking output as well
15755 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15757 if (oldState == newState) return;
15758 oldState = newState;
15759 if (gameMode == EditPosition) EditPositionDone(TRUE);
15761 SendToProgram("post\n", &first);
15762 if (gameMode == TwoMachinesPlay) {
15763 SendToProgram("post\n", &second);
15766 SendToProgram("nopost\n", &first);
15767 thinkOutput[0] = NULLCHAR;
15768 if (gameMode == TwoMachinesPlay) {
15769 SendToProgram("nopost\n", &second);
15772 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15776 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15778 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15779 if (pr == NoProc) return;
15780 AskQuestion(title, question, replyPrefix, pr);
15784 TypeInEvent (char firstChar)
15786 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15787 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15788 gameMode == AnalyzeMode || gameMode == EditGame ||
15789 gameMode == EditPosition || gameMode == IcsExamining ||
15790 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15791 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15792 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15793 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15794 gameMode == Training) PopUpMoveDialog(firstChar);
15798 TypeInDoneEvent (char *move)
15801 int n, fromX, fromY, toX, toY;
15803 ChessMove moveType;
15806 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15807 EditPositionPasteFEN(move);
15810 // [HGM] movenum: allow move number to be typed in any mode
15811 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15815 // undocumented kludge: allow command-line option to be typed in!
15816 // (potentially fatal, and does not implement the effect of the option.)
15817 // should only be used for options that are values on which future decisions will be made,
15818 // and definitely not on options that would be used during initialization.
15819 if(strstr(move, "!!! -") == move) {
15820 ParseArgsFromString(move+4);
15824 if (gameMode != EditGame && currentMove != forwardMostMove &&
15825 gameMode != Training) {
15826 DisplayMoveError(_("Displayed move is not current"));
15828 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15829 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15830 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15831 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15832 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15833 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15835 DisplayMoveError(_("Could not parse move"));
15841 DisplayMove (int moveNumber)
15843 char message[MSG_SIZ];
15845 char cpThinkOutput[MSG_SIZ];
15847 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15849 if (moveNumber == forwardMostMove - 1 ||
15850 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15852 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15854 if (strchr(cpThinkOutput, '\n')) {
15855 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15858 *cpThinkOutput = NULLCHAR;
15861 /* [AS] Hide thinking from human user */
15862 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15863 *cpThinkOutput = NULLCHAR;
15864 if( thinkOutput[0] != NULLCHAR ) {
15867 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15868 cpThinkOutput[i] = '.';
15870 cpThinkOutput[i] = NULLCHAR;
15871 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15875 if (moveNumber == forwardMostMove - 1 &&
15876 gameInfo.resultDetails != NULL) {
15877 if (gameInfo.resultDetails[0] == NULLCHAR) {
15878 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15880 snprintf(res, MSG_SIZ, " {%s} %s",
15881 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15887 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15888 DisplayMessage(res, cpThinkOutput);
15890 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15891 WhiteOnMove(moveNumber) ? " " : ".. ",
15892 parseList[moveNumber], res);
15893 DisplayMessage(message, cpThinkOutput);
15898 DisplayComment (int moveNumber, char *text)
15900 char title[MSG_SIZ];
15902 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15903 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15905 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15906 WhiteOnMove(moveNumber) ? " " : ".. ",
15907 parseList[moveNumber]);
15909 if (text != NULL && (appData.autoDisplayComment || commentUp))
15910 CommentPopUp(title, text);
15913 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15914 * might be busy thinking or pondering. It can be omitted if your
15915 * gnuchess is configured to stop thinking immediately on any user
15916 * input. However, that gnuchess feature depends on the FIONREAD
15917 * ioctl, which does not work properly on some flavors of Unix.
15920 Attention (ChessProgramState *cps)
15923 if (!cps->useSigint) return;
15924 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15925 switch (gameMode) {
15926 case MachinePlaysWhite:
15927 case MachinePlaysBlack:
15928 case TwoMachinesPlay:
15929 case IcsPlayingWhite:
15930 case IcsPlayingBlack:
15933 /* Skip if we know it isn't thinking */
15934 if (!cps->maybeThinking) return;
15935 if (appData.debugMode)
15936 fprintf(debugFP, "Interrupting %s\n", cps->which);
15937 InterruptChildProcess(cps->pr);
15938 cps->maybeThinking = FALSE;
15943 #endif /*ATTENTION*/
15949 if (whiteTimeRemaining <= 0) {
15952 if (appData.icsActive) {
15953 if (appData.autoCallFlag &&
15954 gameMode == IcsPlayingBlack && !blackFlag) {
15955 SendToICS(ics_prefix);
15956 SendToICS("flag\n");
15960 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15962 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15963 if (appData.autoCallFlag) {
15964 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15971 if (blackTimeRemaining <= 0) {
15974 if (appData.icsActive) {
15975 if (appData.autoCallFlag &&
15976 gameMode == IcsPlayingWhite && !whiteFlag) {
15977 SendToICS(ics_prefix);
15978 SendToICS("flag\n");
15982 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15984 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15985 if (appData.autoCallFlag) {
15986 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15997 CheckTimeControl ()
15999 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16000 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16003 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16005 if ( !WhiteOnMove(forwardMostMove) ) {
16006 /* White made time control */
16007 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16008 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16009 /* [HGM] time odds: correct new time quota for time odds! */
16010 / WhitePlayer()->timeOdds;
16011 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16013 lastBlack -= blackTimeRemaining;
16014 /* Black made time control */
16015 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16016 / WhitePlayer()->other->timeOdds;
16017 lastWhite = whiteTimeRemaining;
16022 DisplayBothClocks ()
16024 int wom = gameMode == EditPosition ?
16025 !blackPlaysFirst : WhiteOnMove(currentMove);
16026 DisplayWhiteClock(whiteTimeRemaining, wom);
16027 DisplayBlackClock(blackTimeRemaining, !wom);
16031 /* Timekeeping seems to be a portability nightmare. I think everyone
16032 has ftime(), but I'm really not sure, so I'm including some ifdefs
16033 to use other calls if you don't. Clocks will be less accurate if
16034 you have neither ftime nor gettimeofday.
16037 /* VS 2008 requires the #include outside of the function */
16038 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16039 #include <sys/timeb.h>
16042 /* Get the current time as a TimeMark */
16044 GetTimeMark (TimeMark *tm)
16046 #if HAVE_GETTIMEOFDAY
16048 struct timeval timeVal;
16049 struct timezone timeZone;
16051 gettimeofday(&timeVal, &timeZone);
16052 tm->sec = (long) timeVal.tv_sec;
16053 tm->ms = (int) (timeVal.tv_usec / 1000L);
16055 #else /*!HAVE_GETTIMEOFDAY*/
16058 // include <sys/timeb.h> / moved to just above start of function
16059 struct timeb timeB;
16062 tm->sec = (long) timeB.time;
16063 tm->ms = (int) timeB.millitm;
16065 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16066 tm->sec = (long) time(NULL);
16072 /* Return the difference in milliseconds between two
16073 time marks. We assume the difference will fit in a long!
16076 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16078 return 1000L*(tm2->sec - tm1->sec) +
16079 (long) (tm2->ms - tm1->ms);
16084 * Code to manage the game clocks.
16086 * In tournament play, black starts the clock and then white makes a move.
16087 * We give the human user a slight advantage if he is playing white---the
16088 * clocks don't run until he makes his first move, so it takes zero time.
16089 * Also, we don't account for network lag, so we could get out of sync
16090 * with GNU Chess's clock -- but then, referees are always right.
16093 static TimeMark tickStartTM;
16094 static long intendedTickLength;
16097 NextTickLength (long timeRemaining)
16099 long nominalTickLength, nextTickLength;
16101 if (timeRemaining > 0L && timeRemaining <= 10000L)
16102 nominalTickLength = 100L;
16104 nominalTickLength = 1000L;
16105 nextTickLength = timeRemaining % nominalTickLength;
16106 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16108 return nextTickLength;
16111 /* Adjust clock one minute up or down */
16113 AdjustClock (Boolean which, int dir)
16115 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16116 if(which) blackTimeRemaining += 60000*dir;
16117 else whiteTimeRemaining += 60000*dir;
16118 DisplayBothClocks();
16119 adjustedClock = TRUE;
16122 /* Stop clocks and reset to a fresh time control */
16126 (void) StopClockTimer();
16127 if (appData.icsActive) {
16128 whiteTimeRemaining = blackTimeRemaining = 0;
16129 } else if (searchTime) {
16130 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16131 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16132 } else { /* [HGM] correct new time quote for time odds */
16133 whiteTC = blackTC = fullTimeControlString;
16134 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16135 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16137 if (whiteFlag || blackFlag) {
16139 whiteFlag = blackFlag = FALSE;
16141 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16142 DisplayBothClocks();
16143 adjustedClock = FALSE;
16146 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16148 /* Decrement running clock by amount of time that has passed */
16152 long timeRemaining;
16153 long lastTickLength, fudge;
16156 if (!appData.clockMode) return;
16157 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16161 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16163 /* Fudge if we woke up a little too soon */
16164 fudge = intendedTickLength - lastTickLength;
16165 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16167 if (WhiteOnMove(forwardMostMove)) {
16168 if(whiteNPS >= 0) lastTickLength = 0;
16169 timeRemaining = whiteTimeRemaining -= lastTickLength;
16170 if(timeRemaining < 0 && !appData.icsActive) {
16171 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16172 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16173 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16174 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16177 DisplayWhiteClock(whiteTimeRemaining - fudge,
16178 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16180 if(blackNPS >= 0) lastTickLength = 0;
16181 timeRemaining = blackTimeRemaining -= lastTickLength;
16182 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16183 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16185 blackStartMove = forwardMostMove;
16186 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16189 DisplayBlackClock(blackTimeRemaining - fudge,
16190 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16192 if (CheckFlags()) return;
16195 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16196 StartClockTimer(intendedTickLength);
16198 /* if the time remaining has fallen below the alarm threshold, sound the
16199 * alarm. if the alarm has sounded and (due to a takeback or time control
16200 * with increment) the time remaining has increased to a level above the
16201 * threshold, reset the alarm so it can sound again.
16204 if (appData.icsActive && appData.icsAlarm) {
16206 /* make sure we are dealing with the user's clock */
16207 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16208 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16211 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16212 alarmSounded = FALSE;
16213 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16215 alarmSounded = TRUE;
16221 /* A player has just moved, so stop the previously running
16222 clock and (if in clock mode) start the other one.
16223 We redisplay both clocks in case we're in ICS mode, because
16224 ICS gives us an update to both clocks after every move.
16225 Note that this routine is called *after* forwardMostMove
16226 is updated, so the last fractional tick must be subtracted
16227 from the color that is *not* on move now.
16230 SwitchClocks (int newMoveNr)
16232 long lastTickLength;
16234 int flagged = FALSE;
16238 if (StopClockTimer() && appData.clockMode) {
16239 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16240 if (!WhiteOnMove(forwardMostMove)) {
16241 if(blackNPS >= 0) lastTickLength = 0;
16242 blackTimeRemaining -= lastTickLength;
16243 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16244 // if(pvInfoList[forwardMostMove].time == -1)
16245 pvInfoList[forwardMostMove].time = // use GUI time
16246 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16248 if(whiteNPS >= 0) lastTickLength = 0;
16249 whiteTimeRemaining -= lastTickLength;
16250 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16251 // if(pvInfoList[forwardMostMove].time == -1)
16252 pvInfoList[forwardMostMove].time =
16253 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16255 flagged = CheckFlags();
16257 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16258 CheckTimeControl();
16260 if (flagged || !appData.clockMode) return;
16262 switch (gameMode) {
16263 case MachinePlaysBlack:
16264 case MachinePlaysWhite:
16265 case BeginningOfGame:
16266 if (pausing) return;
16270 case PlayFromGameFile:
16278 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16279 if(WhiteOnMove(forwardMostMove))
16280 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16281 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16285 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16286 whiteTimeRemaining : blackTimeRemaining);
16287 StartClockTimer(intendedTickLength);
16291 /* Stop both clocks */
16295 long lastTickLength;
16298 if (!StopClockTimer()) return;
16299 if (!appData.clockMode) return;
16303 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16304 if (WhiteOnMove(forwardMostMove)) {
16305 if(whiteNPS >= 0) lastTickLength = 0;
16306 whiteTimeRemaining -= lastTickLength;
16307 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16309 if(blackNPS >= 0) lastTickLength = 0;
16310 blackTimeRemaining -= lastTickLength;
16311 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16316 /* Start clock of player on move. Time may have been reset, so
16317 if clock is already running, stop and restart it. */
16321 (void) StopClockTimer(); /* in case it was running already */
16322 DisplayBothClocks();
16323 if (CheckFlags()) return;
16325 if (!appData.clockMode) return;
16326 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16328 GetTimeMark(&tickStartTM);
16329 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16330 whiteTimeRemaining : blackTimeRemaining);
16332 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16333 whiteNPS = blackNPS = -1;
16334 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16335 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16336 whiteNPS = first.nps;
16337 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16338 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16339 blackNPS = first.nps;
16340 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16341 whiteNPS = second.nps;
16342 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16343 blackNPS = second.nps;
16344 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16346 StartClockTimer(intendedTickLength);
16350 TimeString (long ms)
16352 long second, minute, hour, day;
16354 static char buf[32];
16356 if (ms > 0 && ms <= 9900) {
16357 /* convert milliseconds to tenths, rounding up */
16358 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16360 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16364 /* convert milliseconds to seconds, rounding up */
16365 /* use floating point to avoid strangeness of integer division
16366 with negative dividends on many machines */
16367 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16374 day = second / (60 * 60 * 24);
16375 second = second % (60 * 60 * 24);
16376 hour = second / (60 * 60);
16377 second = second % (60 * 60);
16378 minute = second / 60;
16379 second = second % 60;
16382 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16383 sign, day, hour, minute, second);
16385 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16387 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16394 * This is necessary because some C libraries aren't ANSI C compliant yet.
16397 StrStr (char *string, char *match)
16401 length = strlen(match);
16403 for (i = strlen(string) - length; i >= 0; i--, string++)
16404 if (!strncmp(match, string, length))
16411 StrCaseStr (char *string, char *match)
16415 length = strlen(match);
16417 for (i = strlen(string) - length; i >= 0; i--, string++) {
16418 for (j = 0; j < length; j++) {
16419 if (ToLower(match[j]) != ToLower(string[j]))
16422 if (j == length) return string;
16430 StrCaseCmp (char *s1, char *s2)
16435 c1 = ToLower(*s1++);
16436 c2 = ToLower(*s2++);
16437 if (c1 > c2) return 1;
16438 if (c1 < c2) return -1;
16439 if (c1 == NULLCHAR) return 0;
16447 return isupper(c) ? tolower(c) : c;
16454 return islower(c) ? toupper(c) : c;
16456 #endif /* !_amigados */
16463 if ((ret = (char *) malloc(strlen(s) + 1)))
16465 safeStrCpy(ret, s, strlen(s)+1);
16471 StrSavePtr (char *s, char **savePtr)
16476 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16477 safeStrCpy(*savePtr, s, strlen(s)+1);
16489 clock = time((time_t *)NULL);
16490 tm = localtime(&clock);
16491 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16492 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16493 return StrSave(buf);
16498 PositionToFEN (int move, char *overrideCastling)
16500 int i, j, fromX, fromY, toX, toY;
16507 whiteToPlay = (gameMode == EditPosition) ?
16508 !blackPlaysFirst : (move % 2 == 0);
16511 /* Piece placement data */
16512 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16513 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16515 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16516 if (boards[move][i][j] == EmptySquare) {
16518 } else { ChessSquare piece = boards[move][i][j];
16519 if (emptycount > 0) {
16520 if(emptycount<10) /* [HGM] can be >= 10 */
16521 *p++ = '0' + emptycount;
16522 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16525 if(PieceToChar(piece) == '+') {
16526 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16528 piece = (ChessSquare)(DEMOTED piece);
16530 *p++ = PieceToChar(piece);
16532 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16533 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16538 if (emptycount > 0) {
16539 if(emptycount<10) /* [HGM] can be >= 10 */
16540 *p++ = '0' + emptycount;
16541 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16548 /* [HGM] print Crazyhouse or Shogi holdings */
16549 if( gameInfo.holdingsWidth ) {
16550 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16552 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16553 piece = boards[move][i][BOARD_WIDTH-1];
16554 if( piece != EmptySquare )
16555 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16556 *p++ = PieceToChar(piece);
16558 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16559 piece = boards[move][BOARD_HEIGHT-i-1][0];
16560 if( piece != EmptySquare )
16561 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16562 *p++ = PieceToChar(piece);
16565 if( q == p ) *p++ = '-';
16571 *p++ = whiteToPlay ? 'w' : 'b';
16574 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16575 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16577 if(nrCastlingRights) {
16579 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16580 /* [HGM] write directly from rights */
16581 if(boards[move][CASTLING][2] != NoRights &&
16582 boards[move][CASTLING][0] != NoRights )
16583 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16584 if(boards[move][CASTLING][2] != NoRights &&
16585 boards[move][CASTLING][1] != NoRights )
16586 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16587 if(boards[move][CASTLING][5] != NoRights &&
16588 boards[move][CASTLING][3] != NoRights )
16589 *p++ = boards[move][CASTLING][3] + AAA;
16590 if(boards[move][CASTLING][5] != NoRights &&
16591 boards[move][CASTLING][4] != NoRights )
16592 *p++ = boards[move][CASTLING][4] + AAA;
16595 /* [HGM] write true castling rights */
16596 if( nrCastlingRights == 6 ) {
16597 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16598 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16599 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16600 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16601 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16602 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16603 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16604 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16607 if (q == p) *p++ = '-'; /* No castling rights */
16611 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16612 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16613 /* En passant target square */
16614 if (move > backwardMostMove) {
16615 fromX = moveList[move - 1][0] - AAA;
16616 fromY = moveList[move - 1][1] - ONE;
16617 toX = moveList[move - 1][2] - AAA;
16618 toY = moveList[move - 1][3] - ONE;
16619 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16620 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16621 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16623 /* 2-square pawn move just happened */
16625 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16629 } else if(move == backwardMostMove) {
16630 // [HGM] perhaps we should always do it like this, and forget the above?
16631 if((signed char)boards[move][EP_STATUS] >= 0) {
16632 *p++ = boards[move][EP_STATUS] + AAA;
16633 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16644 /* [HGM] find reversible plies */
16645 { int i = 0, j=move;
16647 if (appData.debugMode) { int k;
16648 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16649 for(k=backwardMostMove; k<=forwardMostMove; k++)
16650 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16654 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16655 if( j == backwardMostMove ) i += initialRulePlies;
16656 sprintf(p, "%d ", i);
16657 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16659 /* Fullmove number */
16660 sprintf(p, "%d", (move / 2) + 1);
16662 return StrSave(buf);
16666 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16675 /* [HGM] by default clear Crazyhouse holdings, if present */
16676 if(gameInfo.holdingsWidth) {
16677 for(i=0; i<BOARD_HEIGHT; i++) {
16678 board[i][0] = EmptySquare; /* black holdings */
16679 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16680 board[i][1] = (ChessSquare) 0; /* black counts */
16681 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16685 /* Piece placement data */
16686 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16689 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16690 if (*p == '/') p++;
16691 emptycount = gameInfo.boardWidth - j;
16692 while (emptycount--)
16693 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16695 #if(BOARD_FILES >= 10)
16696 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16697 p++; emptycount=10;
16698 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16699 while (emptycount--)
16700 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16702 } else if (isdigit(*p)) {
16703 emptycount = *p++ - '0';
16704 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16705 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16706 while (emptycount--)
16707 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16708 } else if (*p == '+' || isalpha(*p)) {
16709 if (j >= gameInfo.boardWidth) return FALSE;
16711 piece = CharToPiece(*++p);
16712 if(piece == EmptySquare) return FALSE; /* unknown piece */
16713 piece = (ChessSquare) (PROMOTED piece ); p++;
16714 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16715 } else piece = CharToPiece(*p++);
16717 if(piece==EmptySquare) return FALSE; /* unknown piece */
16718 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16719 piece = (ChessSquare) (PROMOTED piece);
16720 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16723 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16729 while (*p == '/' || *p == ' ') p++;
16731 /* [HGM] look for Crazyhouse holdings here */
16732 while(*p==' ') p++;
16733 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16735 if(*p == '-' ) p++; /* empty holdings */ else {
16736 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16737 /* if we would allow FEN reading to set board size, we would */
16738 /* have to add holdings and shift the board read so far here */
16739 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16741 if((int) piece >= (int) BlackPawn ) {
16742 i = (int)piece - (int)BlackPawn;
16743 i = PieceToNumber((ChessSquare)i);
16744 if( i >= gameInfo.holdingsSize ) return FALSE;
16745 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16746 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16748 i = (int)piece - (int)WhitePawn;
16749 i = PieceToNumber((ChessSquare)i);
16750 if( i >= gameInfo.holdingsSize ) return FALSE;
16751 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16752 board[i][BOARD_WIDTH-2]++; /* black holdings */
16759 while(*p == ' ') p++;
16763 if(appData.colorNickNames) {
16764 if( c == appData.colorNickNames[0] ) c = 'w'; else
16765 if( c == appData.colorNickNames[1] ) c = 'b';
16769 *blackPlaysFirst = FALSE;
16772 *blackPlaysFirst = TRUE;
16778 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16779 /* return the extra info in global variiables */
16781 /* set defaults in case FEN is incomplete */
16782 board[EP_STATUS] = EP_UNKNOWN;
16783 for(i=0; i<nrCastlingRights; i++ ) {
16784 board[CASTLING][i] =
16785 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16786 } /* assume possible unless obviously impossible */
16787 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16788 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16789 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16790 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16791 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16792 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16793 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16794 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16797 while(*p==' ') p++;
16798 if(nrCastlingRights) {
16799 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16800 /* castling indicator present, so default becomes no castlings */
16801 for(i=0; i<nrCastlingRights; i++ ) {
16802 board[CASTLING][i] = NoRights;
16805 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16806 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16807 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16808 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16809 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16811 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16812 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16813 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16815 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16816 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16817 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16818 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16819 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16820 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16823 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16824 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16825 board[CASTLING][2] = whiteKingFile;
16828 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16829 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16830 board[CASTLING][2] = whiteKingFile;
16833 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16834 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16835 board[CASTLING][5] = blackKingFile;
16838 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16839 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16840 board[CASTLING][5] = blackKingFile;
16843 default: /* FRC castlings */
16844 if(c >= 'a') { /* black rights */
16845 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16846 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16847 if(i == BOARD_RGHT) break;
16848 board[CASTLING][5] = i;
16850 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16851 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16853 board[CASTLING][3] = c;
16855 board[CASTLING][4] = c;
16856 } else { /* white rights */
16857 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16858 if(board[0][i] == WhiteKing) break;
16859 if(i == BOARD_RGHT) break;
16860 board[CASTLING][2] = i;
16861 c -= AAA - 'a' + 'A';
16862 if(board[0][c] >= WhiteKing) break;
16864 board[CASTLING][0] = c;
16866 board[CASTLING][1] = c;
16870 for(i=0; i<nrCastlingRights; i++)
16871 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16872 if (appData.debugMode) {
16873 fprintf(debugFP, "FEN castling rights:");
16874 for(i=0; i<nrCastlingRights; i++)
16875 fprintf(debugFP, " %d", board[CASTLING][i]);
16876 fprintf(debugFP, "\n");
16879 while(*p==' ') p++;
16882 /* read e.p. field in games that know e.p. capture */
16883 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16884 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16886 p++; board[EP_STATUS] = EP_NONE;
16888 char c = *p++ - AAA;
16890 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16891 if(*p >= '0' && *p <='9') p++;
16892 board[EP_STATUS] = c;
16897 if(sscanf(p, "%d", &i) == 1) {
16898 FENrulePlies = i; /* 50-move ply counter */
16899 /* (The move number is still ignored) */
16906 EditPositionPasteFEN (char *fen)
16909 Board initial_position;
16911 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16912 DisplayError(_("Bad FEN position in clipboard"), 0);
16915 int savedBlackPlaysFirst = blackPlaysFirst;
16916 EditPositionEvent();
16917 blackPlaysFirst = savedBlackPlaysFirst;
16918 CopyBoard(boards[0], initial_position);
16919 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16920 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16921 DisplayBothClocks();
16922 DrawPosition(FALSE, boards[currentMove]);
16927 static char cseq[12] = "\\ ";
16930 set_cont_sequence (char *new_seq)
16935 // handle bad attempts to set the sequence
16937 return 0; // acceptable error - no debug
16939 len = strlen(new_seq);
16940 ret = (len > 0) && (len < sizeof(cseq));
16942 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16943 else if (appData.debugMode)
16944 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16949 reformat a source message so words don't cross the width boundary. internal
16950 newlines are not removed. returns the wrapped size (no null character unless
16951 included in source message). If dest is NULL, only calculate the size required
16952 for the dest buffer. lp argument indicats line position upon entry, and it's
16953 passed back upon exit.
16956 wrap (char *dest, char *src, int count, int width, int *lp)
16958 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16960 cseq_len = strlen(cseq);
16961 old_line = line = *lp;
16962 ansi = len = clen = 0;
16964 for (i=0; i < count; i++)
16966 if (src[i] == '\033')
16969 // if we hit the width, back up
16970 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16972 // store i & len in case the word is too long
16973 old_i = i, old_len = len;
16975 // find the end of the last word
16976 while (i && src[i] != ' ' && src[i] != '\n')
16982 // word too long? restore i & len before splitting it
16983 if ((old_i-i+clen) >= width)
16990 if (i && src[i-1] == ' ')
16993 if (src[i] != ' ' && src[i] != '\n')
17000 // now append the newline and continuation sequence
17005 strncpy(dest+len, cseq, cseq_len);
17013 dest[len] = src[i];
17017 if (src[i] == '\n')
17022 if (dest && appData.debugMode)
17024 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17025 count, width, line, len, *lp);
17026 show_bytes(debugFP, src, count);
17027 fprintf(debugFP, "\ndest: ");
17028 show_bytes(debugFP, dest, len);
17029 fprintf(debugFP, "\n");
17031 *lp = dest ? line : old_line;
17036 // [HGM] vari: routines for shelving variations
17037 Boolean modeRestore = FALSE;
17040 PushInner (int firstMove, int lastMove)
17042 int i, j, nrMoves = lastMove - firstMove;
17044 // push current tail of game on stack
17045 savedResult[storedGames] = gameInfo.result;
17046 savedDetails[storedGames] = gameInfo.resultDetails;
17047 gameInfo.resultDetails = NULL;
17048 savedFirst[storedGames] = firstMove;
17049 savedLast [storedGames] = lastMove;
17050 savedFramePtr[storedGames] = framePtr;
17051 framePtr -= nrMoves; // reserve space for the boards
17052 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17053 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17054 for(j=0; j<MOVE_LEN; j++)
17055 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17056 for(j=0; j<2*MOVE_LEN; j++)
17057 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17058 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17059 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17060 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17061 pvInfoList[firstMove+i-1].depth = 0;
17062 commentList[framePtr+i] = commentList[firstMove+i];
17063 commentList[firstMove+i] = NULL;
17067 forwardMostMove = firstMove; // truncate game so we can start variation
17071 PushTail (int firstMove, int lastMove)
17073 if(appData.icsActive) { // only in local mode
17074 forwardMostMove = currentMove; // mimic old ICS behavior
17077 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17079 PushInner(firstMove, lastMove);
17080 if(storedGames == 1) GreyRevert(FALSE);
17081 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17085 PopInner (Boolean annotate)
17088 char buf[8000], moveBuf[20];
17090 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17091 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17092 nrMoves = savedLast[storedGames] - currentMove;
17095 if(!WhiteOnMove(currentMove))
17096 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17097 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17098 for(i=currentMove; i<forwardMostMove; i++) {
17100 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17101 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17102 strcat(buf, moveBuf);
17103 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17104 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17108 for(i=1; i<=nrMoves; i++) { // copy last variation back
17109 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17110 for(j=0; j<MOVE_LEN; j++)
17111 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17112 for(j=0; j<2*MOVE_LEN; j++)
17113 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17114 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17115 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17116 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17117 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17118 commentList[currentMove+i] = commentList[framePtr+i];
17119 commentList[framePtr+i] = NULL;
17121 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17122 framePtr = savedFramePtr[storedGames];
17123 gameInfo.result = savedResult[storedGames];
17124 if(gameInfo.resultDetails != NULL) {
17125 free(gameInfo.resultDetails);
17127 gameInfo.resultDetails = savedDetails[storedGames];
17128 forwardMostMove = currentMove + nrMoves;
17132 PopTail (Boolean annotate)
17134 if(appData.icsActive) return FALSE; // only in local mode
17135 if(!storedGames) return FALSE; // sanity
17136 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17138 PopInner(annotate);
17139 if(currentMove < forwardMostMove) ForwardEvent(); else
17140 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17142 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17148 { // remove all shelved variations
17150 for(i=0; i<storedGames; i++) {
17151 if(savedDetails[i])
17152 free(savedDetails[i]);
17153 savedDetails[i] = NULL;
17155 for(i=framePtr; i<MAX_MOVES; i++) {
17156 if(commentList[i]) free(commentList[i]);
17157 commentList[i] = NULL;
17159 framePtr = MAX_MOVES-1;
17164 LoadVariation (int index, char *text)
17165 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17166 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17167 int level = 0, move;
17169 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17170 // first find outermost bracketing variation
17171 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17172 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17173 if(*p == '{') wait = '}'; else
17174 if(*p == '[') wait = ']'; else
17175 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17176 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17178 if(*p == wait) wait = NULLCHAR; // closing ]} found
17181 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17182 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17183 end[1] = NULLCHAR; // clip off comment beyond variation
17184 ToNrEvent(currentMove-1);
17185 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17186 // kludge: use ParsePV() to append variation to game
17187 move = currentMove;
17188 ParsePV(start, TRUE, TRUE);
17189 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17190 ClearPremoveHighlights();
17192 ToNrEvent(currentMove+1);