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 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13557 DisplayError("second engine does not play this", 0);
13561 switch (gameMode) {
13562 case TwoMachinesPlay:
13564 case MachinePlaysWhite:
13565 case MachinePlaysBlack:
13566 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13567 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13571 case BeginningOfGame:
13572 case PlayFromGameFile:
13575 if (gameMode != EditGame) return;
13578 EditPositionDone(TRUE);
13589 // forwardMostMove = currentMove;
13590 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13592 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13594 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13595 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13596 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13600 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13601 SendToProgram("force\n", &second);
13603 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13606 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13607 if(appData.matchPause>10000 || appData.matchPause<10)
13608 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13609 wait = SubtractTimeMarks(&now, &pauseStart);
13610 if(wait < appData.matchPause) {
13611 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13614 // we are now committed to starting the game
13616 DisplayMessage("", "");
13617 if (startedFromSetupPosition) {
13618 SendBoard(&second, backwardMostMove);
13619 if (appData.debugMode) {
13620 fprintf(debugFP, "Two Machines\n");
13623 for (i = backwardMostMove; i < forwardMostMove; i++) {
13624 SendMoveToProgram(i, &second);
13627 gameMode = TwoMachinesPlay;
13629 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13631 DisplayTwoMachinesTitle();
13633 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13638 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13639 SendToProgram(first.computerString, &first);
13640 if (first.sendName) {
13641 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13642 SendToProgram(buf, &first);
13644 SendToProgram(second.computerString, &second);
13645 if (second.sendName) {
13646 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13647 SendToProgram(buf, &second);
13651 if (!first.sendTime || !second.sendTime) {
13652 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13653 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13655 if (onmove->sendTime) {
13656 if (onmove->useColors) {
13657 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13659 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13661 if (onmove->useColors) {
13662 SendToProgram(onmove->twoMachinesColor, onmove);
13664 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13665 // SendToProgram("go\n", onmove);
13666 onmove->maybeThinking = TRUE;
13667 SetMachineThinkingEnables();
13671 if(bookHit) { // [HGM] book: simulate book reply
13672 static char bookMove[MSG_SIZ]; // a bit generous?
13674 programStats.nodes = programStats.depth = programStats.time =
13675 programStats.score = programStats.got_only_move = 0;
13676 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13678 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13679 strcat(bookMove, bookHit);
13680 savedMessage = bookMove; // args for deferred call
13681 savedState = onmove;
13682 ScheduleDelayedEvent(DeferredBookMove, 1);
13689 if (gameMode == Training) {
13690 SetTrainingModeOff();
13691 gameMode = PlayFromGameFile;
13692 DisplayMessage("", _("Training mode off"));
13694 gameMode = Training;
13695 animateTraining = appData.animate;
13697 /* make sure we are not already at the end of the game */
13698 if (currentMove < forwardMostMove) {
13699 SetTrainingModeOn();
13700 DisplayMessage("", _("Training mode on"));
13702 gameMode = PlayFromGameFile;
13703 DisplayError(_("Already at end of game"), 0);
13712 if (!appData.icsActive) return;
13713 switch (gameMode) {
13714 case IcsPlayingWhite:
13715 case IcsPlayingBlack:
13718 case BeginningOfGame:
13726 EditPositionDone(TRUE);
13739 gameMode = IcsIdle;
13749 switch (gameMode) {
13751 SetTrainingModeOff();
13753 case MachinePlaysWhite:
13754 case MachinePlaysBlack:
13755 case BeginningOfGame:
13756 SendToProgram("force\n", &first);
13757 SetUserThinkingEnables();
13759 case PlayFromGameFile:
13760 (void) StopLoadGameTimer();
13761 if (gameFileFP != NULL) {
13766 EditPositionDone(TRUE);
13771 SendToProgram("force\n", &first);
13773 case TwoMachinesPlay:
13774 GameEnds(EndOfFile, NULL, GE_PLAYER);
13775 ResurrectChessProgram();
13776 SetUserThinkingEnables();
13779 ResurrectChessProgram();
13781 case IcsPlayingBlack:
13782 case IcsPlayingWhite:
13783 DisplayError(_("Warning: You are still playing a game"), 0);
13786 DisplayError(_("Warning: You are still observing a game"), 0);
13789 DisplayError(_("Warning: You are still examining a game"), 0);
13800 first.offeredDraw = second.offeredDraw = 0;
13802 if (gameMode == PlayFromGameFile) {
13803 whiteTimeRemaining = timeRemaining[0][currentMove];
13804 blackTimeRemaining = timeRemaining[1][currentMove];
13808 if (gameMode == MachinePlaysWhite ||
13809 gameMode == MachinePlaysBlack ||
13810 gameMode == TwoMachinesPlay ||
13811 gameMode == EndOfGame) {
13812 i = forwardMostMove;
13813 while (i > currentMove) {
13814 SendToProgram("undo\n", &first);
13817 if(!adjustedClock) {
13818 whiteTimeRemaining = timeRemaining[0][currentMove];
13819 blackTimeRemaining = timeRemaining[1][currentMove];
13820 DisplayBothClocks();
13822 if (whiteFlag || blackFlag) {
13823 whiteFlag = blackFlag = 0;
13828 gameMode = EditGame;
13835 EditPositionEvent ()
13837 if (gameMode == EditPosition) {
13843 if (gameMode != EditGame) return;
13845 gameMode = EditPosition;
13848 if (currentMove > 0)
13849 CopyBoard(boards[0], boards[currentMove]);
13851 blackPlaysFirst = !WhiteOnMove(currentMove);
13853 currentMove = forwardMostMove = backwardMostMove = 0;
13854 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13856 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13862 /* [DM] icsEngineAnalyze - possible call from other functions */
13863 if (appData.icsEngineAnalyze) {
13864 appData.icsEngineAnalyze = FALSE;
13866 DisplayMessage("",_("Close ICS engine analyze..."));
13868 if (first.analysisSupport && first.analyzing) {
13869 SendToProgram("exit\n", &first);
13870 first.analyzing = FALSE;
13872 thinkOutput[0] = NULLCHAR;
13876 EditPositionDone (Boolean fakeRights)
13878 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13880 startedFromSetupPosition = TRUE;
13881 InitChessProgram(&first, FALSE);
13882 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13883 boards[0][EP_STATUS] = EP_NONE;
13884 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13885 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13886 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13887 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13888 } else boards[0][CASTLING][2] = NoRights;
13889 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13890 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13891 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13892 } else boards[0][CASTLING][5] = NoRights;
13894 SendToProgram("force\n", &first);
13895 if (blackPlaysFirst) {
13896 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13897 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13898 currentMove = forwardMostMove = backwardMostMove = 1;
13899 CopyBoard(boards[1], boards[0]);
13901 currentMove = forwardMostMove = backwardMostMove = 0;
13903 SendBoard(&first, forwardMostMove);
13904 if (appData.debugMode) {
13905 fprintf(debugFP, "EditPosDone\n");
13908 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13909 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13910 gameMode = EditGame;
13912 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13913 ClearHighlights(); /* [AS] */
13916 /* Pause for `ms' milliseconds */
13917 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13919 TimeDelay (long ms)
13926 } while (SubtractTimeMarks(&m2, &m1) < ms);
13929 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13931 SendMultiLineToICS (char *buf)
13933 char temp[MSG_SIZ+1], *p;
13940 strncpy(temp, buf, len);
13945 if (*p == '\n' || *p == '\r')
13950 strcat(temp, "\n");
13952 SendToPlayer(temp, strlen(temp));
13956 SetWhiteToPlayEvent ()
13958 if (gameMode == EditPosition) {
13959 blackPlaysFirst = FALSE;
13960 DisplayBothClocks(); /* works because currentMove is 0 */
13961 } else if (gameMode == IcsExamining) {
13962 SendToICS(ics_prefix);
13963 SendToICS("tomove white\n");
13968 SetBlackToPlayEvent ()
13970 if (gameMode == EditPosition) {
13971 blackPlaysFirst = TRUE;
13972 currentMove = 1; /* kludge */
13973 DisplayBothClocks();
13975 } else if (gameMode == IcsExamining) {
13976 SendToICS(ics_prefix);
13977 SendToICS("tomove black\n");
13982 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13985 ChessSquare piece = boards[0][y][x];
13987 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13989 switch (selection) {
13991 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13992 SendToICS(ics_prefix);
13993 SendToICS("bsetup clear\n");
13994 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13995 SendToICS(ics_prefix);
13996 SendToICS("clearboard\n");
13998 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13999 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14000 for (y = 0; y < BOARD_HEIGHT; y++) {
14001 if (gameMode == IcsExamining) {
14002 if (boards[currentMove][y][x] != EmptySquare) {
14003 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14008 boards[0][y][x] = p;
14013 if (gameMode == EditPosition) {
14014 DrawPosition(FALSE, boards[0]);
14019 SetWhiteToPlayEvent();
14023 SetBlackToPlayEvent();
14027 if (gameMode == IcsExamining) {
14028 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14029 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14032 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14033 if(x == BOARD_LEFT-2) {
14034 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14035 boards[0][y][1] = 0;
14037 if(x == BOARD_RGHT+1) {
14038 if(y >= gameInfo.holdingsSize) break;
14039 boards[0][y][BOARD_WIDTH-2] = 0;
14042 boards[0][y][x] = EmptySquare;
14043 DrawPosition(FALSE, boards[0]);
14048 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14049 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14050 selection = (ChessSquare) (PROMOTED piece);
14051 } else if(piece == EmptySquare) selection = WhiteSilver;
14052 else selection = (ChessSquare)((int)piece - 1);
14056 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14057 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14058 selection = (ChessSquare) (DEMOTED piece);
14059 } else if(piece == EmptySquare) selection = BlackSilver;
14060 else selection = (ChessSquare)((int)piece + 1);
14065 if(gameInfo.variant == VariantShatranj ||
14066 gameInfo.variant == VariantXiangqi ||
14067 gameInfo.variant == VariantCourier ||
14068 gameInfo.variant == VariantMakruk )
14069 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14074 if(gameInfo.variant == VariantXiangqi)
14075 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14076 if(gameInfo.variant == VariantKnightmate)
14077 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14080 if (gameMode == IcsExamining) {
14081 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14082 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14083 PieceToChar(selection), AAA + x, ONE + y);
14086 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14088 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14089 n = PieceToNumber(selection - BlackPawn);
14090 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14091 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14092 boards[0][BOARD_HEIGHT-1-n][1]++;
14094 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14095 n = PieceToNumber(selection);
14096 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14097 boards[0][n][BOARD_WIDTH-1] = selection;
14098 boards[0][n][BOARD_WIDTH-2]++;
14101 boards[0][y][x] = selection;
14102 DrawPosition(TRUE, boards[0]);
14104 fromX = fromY = -1;
14112 DropMenuEvent (ChessSquare selection, int x, int y)
14114 ChessMove moveType;
14116 switch (gameMode) {
14117 case IcsPlayingWhite:
14118 case MachinePlaysBlack:
14119 if (!WhiteOnMove(currentMove)) {
14120 DisplayMoveError(_("It is Black's turn"));
14123 moveType = WhiteDrop;
14125 case IcsPlayingBlack:
14126 case MachinePlaysWhite:
14127 if (WhiteOnMove(currentMove)) {
14128 DisplayMoveError(_("It is White's turn"));
14131 moveType = BlackDrop;
14134 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14140 if (moveType == BlackDrop && selection < BlackPawn) {
14141 selection = (ChessSquare) ((int) selection
14142 + (int) BlackPawn - (int) WhitePawn);
14144 if (boards[currentMove][y][x] != EmptySquare) {
14145 DisplayMoveError(_("That square is occupied"));
14149 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14155 /* Accept a pending offer of any kind from opponent */
14157 if (appData.icsActive) {
14158 SendToICS(ics_prefix);
14159 SendToICS("accept\n");
14160 } else if (cmailMsgLoaded) {
14161 if (currentMove == cmailOldMove &&
14162 commentList[cmailOldMove] != NULL &&
14163 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14164 "Black offers a draw" : "White offers a draw")) {
14166 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14167 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14169 DisplayError(_("There is no pending offer on this move"), 0);
14170 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14173 /* Not used for offers from chess program */
14180 /* Decline a pending offer of any kind from opponent */
14182 if (appData.icsActive) {
14183 SendToICS(ics_prefix);
14184 SendToICS("decline\n");
14185 } else if (cmailMsgLoaded) {
14186 if (currentMove == cmailOldMove &&
14187 commentList[cmailOldMove] != NULL &&
14188 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14189 "Black offers a draw" : "White offers a draw")) {
14191 AppendComment(cmailOldMove, "Draw declined", TRUE);
14192 DisplayComment(cmailOldMove - 1, "Draw declined");
14195 DisplayError(_("There is no pending offer on this move"), 0);
14198 /* Not used for offers from chess program */
14205 /* Issue ICS rematch command */
14206 if (appData.icsActive) {
14207 SendToICS(ics_prefix);
14208 SendToICS("rematch\n");
14215 /* Call your opponent's flag (claim a win on time) */
14216 if (appData.icsActive) {
14217 SendToICS(ics_prefix);
14218 SendToICS("flag\n");
14220 switch (gameMode) {
14223 case MachinePlaysWhite:
14226 GameEnds(GameIsDrawn, "Both players ran out of time",
14229 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14231 DisplayError(_("Your opponent is not out of time"), 0);
14234 case MachinePlaysBlack:
14237 GameEnds(GameIsDrawn, "Both players ran out of time",
14240 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14242 DisplayError(_("Your opponent is not out of time"), 0);
14250 ClockClick (int which)
14251 { // [HGM] code moved to back-end from winboard.c
14252 if(which) { // black clock
14253 if (gameMode == EditPosition || gameMode == IcsExamining) {
14254 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14255 SetBlackToPlayEvent();
14256 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14257 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14258 } else if (shiftKey) {
14259 AdjustClock(which, -1);
14260 } else if (gameMode == IcsPlayingWhite ||
14261 gameMode == MachinePlaysBlack) {
14264 } else { // white clock
14265 if (gameMode == EditPosition || gameMode == IcsExamining) {
14266 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14267 SetWhiteToPlayEvent();
14268 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14269 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14270 } else if (shiftKey) {
14271 AdjustClock(which, -1);
14272 } else if (gameMode == IcsPlayingBlack ||
14273 gameMode == MachinePlaysWhite) {
14282 /* Offer draw or accept pending draw offer from opponent */
14284 if (appData.icsActive) {
14285 /* Note: tournament rules require draw offers to be
14286 made after you make your move but before you punch
14287 your clock. Currently ICS doesn't let you do that;
14288 instead, you immediately punch your clock after making
14289 a move, but you can offer a draw at any time. */
14291 SendToICS(ics_prefix);
14292 SendToICS("draw\n");
14293 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14294 } else if (cmailMsgLoaded) {
14295 if (currentMove == cmailOldMove &&
14296 commentList[cmailOldMove] != NULL &&
14297 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14298 "Black offers a draw" : "White offers a draw")) {
14299 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14301 } else if (currentMove == cmailOldMove + 1) {
14302 char *offer = WhiteOnMove(cmailOldMove) ?
14303 "White offers a draw" : "Black offers a draw";
14304 AppendComment(currentMove, offer, TRUE);
14305 DisplayComment(currentMove - 1, offer);
14306 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14308 DisplayError(_("You must make your move before offering a draw"), 0);
14309 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14311 } else if (first.offeredDraw) {
14312 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14314 if (first.sendDrawOffers) {
14315 SendToProgram("draw\n", &first);
14316 userOfferedDraw = TRUE;
14324 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14326 if (appData.icsActive) {
14327 SendToICS(ics_prefix);
14328 SendToICS("adjourn\n");
14330 /* Currently GNU Chess doesn't offer or accept Adjourns */
14338 /* Offer Abort or accept pending Abort offer from opponent */
14340 if (appData.icsActive) {
14341 SendToICS(ics_prefix);
14342 SendToICS("abort\n");
14344 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14351 /* Resign. You can do this even if it's not your turn. */
14353 if (appData.icsActive) {
14354 SendToICS(ics_prefix);
14355 SendToICS("resign\n");
14357 switch (gameMode) {
14358 case MachinePlaysWhite:
14359 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14361 case MachinePlaysBlack:
14362 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14365 if (cmailMsgLoaded) {
14367 if (WhiteOnMove(cmailOldMove)) {
14368 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14370 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14372 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14383 StopObservingEvent ()
14385 /* Stop observing current games */
14386 SendToICS(ics_prefix);
14387 SendToICS("unobserve\n");
14391 StopExaminingEvent ()
14393 /* Stop observing current game */
14394 SendToICS(ics_prefix);
14395 SendToICS("unexamine\n");
14399 ForwardInner (int target)
14401 int limit; int oldSeekGraphUp = seekGraphUp;
14403 if (appData.debugMode)
14404 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14405 target, currentMove, forwardMostMove);
14407 if (gameMode == EditPosition)
14410 seekGraphUp = FALSE;
14411 MarkTargetSquares(1);
14413 if (gameMode == PlayFromGameFile && !pausing)
14416 if (gameMode == IcsExamining && pausing)
14417 limit = pauseExamForwardMostMove;
14419 limit = forwardMostMove;
14421 if (target > limit) target = limit;
14423 if (target > 0 && moveList[target - 1][0]) {
14424 int fromX, fromY, toX, toY;
14425 toX = moveList[target - 1][2] - AAA;
14426 toY = moveList[target - 1][3] - ONE;
14427 if (moveList[target - 1][1] == '@') {
14428 if (appData.highlightLastMove) {
14429 SetHighlights(-1, -1, toX, toY);
14432 fromX = moveList[target - 1][0] - AAA;
14433 fromY = moveList[target - 1][1] - ONE;
14434 if (target == currentMove + 1) {
14435 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14437 if (appData.highlightLastMove) {
14438 SetHighlights(fromX, fromY, toX, toY);
14442 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14443 gameMode == Training || gameMode == PlayFromGameFile ||
14444 gameMode == AnalyzeFile) {
14445 while (currentMove < target) {
14446 SendMoveToProgram(currentMove++, &first);
14449 currentMove = target;
14452 if (gameMode == EditGame || gameMode == EndOfGame) {
14453 whiteTimeRemaining = timeRemaining[0][currentMove];
14454 blackTimeRemaining = timeRemaining[1][currentMove];
14456 DisplayBothClocks();
14457 DisplayMove(currentMove - 1);
14458 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14459 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14460 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14461 DisplayComment(currentMove - 1, commentList[currentMove]);
14463 ClearMap(); // [HGM] exclude: invalidate map
14470 if (gameMode == IcsExamining && !pausing) {
14471 SendToICS(ics_prefix);
14472 SendToICS("forward\n");
14474 ForwardInner(currentMove + 1);
14481 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14482 /* to optimze, we temporarily turn off analysis mode while we feed
14483 * the remaining moves to the engine. Otherwise we get analysis output
14486 if (first.analysisSupport) {
14487 SendToProgram("exit\nforce\n", &first);
14488 first.analyzing = FALSE;
14492 if (gameMode == IcsExamining && !pausing) {
14493 SendToICS(ics_prefix);
14494 SendToICS("forward 999999\n");
14496 ForwardInner(forwardMostMove);
14499 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14500 /* we have fed all the moves, so reactivate analysis mode */
14501 SendToProgram("analyze\n", &first);
14502 first.analyzing = TRUE;
14503 /*first.maybeThinking = TRUE;*/
14504 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14509 BackwardInner (int target)
14511 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14513 if (appData.debugMode)
14514 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14515 target, currentMove, forwardMostMove);
14517 if (gameMode == EditPosition) return;
14518 seekGraphUp = FALSE;
14519 MarkTargetSquares(1);
14520 if (currentMove <= backwardMostMove) {
14522 DrawPosition(full_redraw, boards[currentMove]);
14525 if (gameMode == PlayFromGameFile && !pausing)
14528 if (moveList[target][0]) {
14529 int fromX, fromY, toX, toY;
14530 toX = moveList[target][2] - AAA;
14531 toY = moveList[target][3] - ONE;
14532 if (moveList[target][1] == '@') {
14533 if (appData.highlightLastMove) {
14534 SetHighlights(-1, -1, toX, toY);
14537 fromX = moveList[target][0] - AAA;
14538 fromY = moveList[target][1] - ONE;
14539 if (target == currentMove - 1) {
14540 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14542 if (appData.highlightLastMove) {
14543 SetHighlights(fromX, fromY, toX, toY);
14547 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14548 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14549 while (currentMove > target) {
14550 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14551 // null move cannot be undone. Reload program with move history before it.
14553 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14554 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14556 SendBoard(&first, i);
14557 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14560 SendToProgram("undo\n", &first);
14564 currentMove = target;
14567 if (gameMode == EditGame || gameMode == EndOfGame) {
14568 whiteTimeRemaining = timeRemaining[0][currentMove];
14569 blackTimeRemaining = timeRemaining[1][currentMove];
14571 DisplayBothClocks();
14572 DisplayMove(currentMove - 1);
14573 DrawPosition(full_redraw, boards[currentMove]);
14574 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14575 // [HGM] PV info: routine tests if comment empty
14576 DisplayComment(currentMove - 1, commentList[currentMove]);
14577 ClearMap(); // [HGM] exclude: invalidate map
14583 if (gameMode == IcsExamining && !pausing) {
14584 SendToICS(ics_prefix);
14585 SendToICS("backward\n");
14587 BackwardInner(currentMove - 1);
14594 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14595 /* to optimize, we temporarily turn off analysis mode while we undo
14596 * all the moves. Otherwise we get analysis output after each undo.
14598 if (first.analysisSupport) {
14599 SendToProgram("exit\nforce\n", &first);
14600 first.analyzing = FALSE;
14604 if (gameMode == IcsExamining && !pausing) {
14605 SendToICS(ics_prefix);
14606 SendToICS("backward 999999\n");
14608 BackwardInner(backwardMostMove);
14611 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14612 /* we have fed all the moves, so reactivate analysis mode */
14613 SendToProgram("analyze\n", &first);
14614 first.analyzing = TRUE;
14615 /*first.maybeThinking = TRUE;*/
14616 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14623 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14624 if (to >= forwardMostMove) to = forwardMostMove;
14625 if (to <= backwardMostMove) to = backwardMostMove;
14626 if (to < currentMove) {
14634 RevertEvent (Boolean annotate)
14636 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14639 if (gameMode != IcsExamining) {
14640 DisplayError(_("You are not examining a game"), 0);
14644 DisplayError(_("You can't revert while pausing"), 0);
14647 SendToICS(ics_prefix);
14648 SendToICS("revert\n");
14652 RetractMoveEvent ()
14654 switch (gameMode) {
14655 case MachinePlaysWhite:
14656 case MachinePlaysBlack:
14657 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14658 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14661 if (forwardMostMove < 2) return;
14662 currentMove = forwardMostMove = forwardMostMove - 2;
14663 whiteTimeRemaining = timeRemaining[0][currentMove];
14664 blackTimeRemaining = timeRemaining[1][currentMove];
14665 DisplayBothClocks();
14666 DisplayMove(currentMove - 1);
14667 ClearHighlights();/*!! could figure this out*/
14668 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14669 SendToProgram("remove\n", &first);
14670 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14673 case BeginningOfGame:
14677 case IcsPlayingWhite:
14678 case IcsPlayingBlack:
14679 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14680 SendToICS(ics_prefix);
14681 SendToICS("takeback 2\n");
14683 SendToICS(ics_prefix);
14684 SendToICS("takeback 1\n");
14693 ChessProgramState *cps;
14695 switch (gameMode) {
14696 case MachinePlaysWhite:
14697 if (!WhiteOnMove(forwardMostMove)) {
14698 DisplayError(_("It is your turn"), 0);
14703 case MachinePlaysBlack:
14704 if (WhiteOnMove(forwardMostMove)) {
14705 DisplayError(_("It is your turn"), 0);
14710 case TwoMachinesPlay:
14711 if (WhiteOnMove(forwardMostMove) ==
14712 (first.twoMachinesColor[0] == 'w')) {
14718 case BeginningOfGame:
14722 SendToProgram("?\n", cps);
14726 TruncateGameEvent ()
14729 if (gameMode != EditGame) return;
14736 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14737 if (forwardMostMove > currentMove) {
14738 if (gameInfo.resultDetails != NULL) {
14739 free(gameInfo.resultDetails);
14740 gameInfo.resultDetails = NULL;
14741 gameInfo.result = GameUnfinished;
14743 forwardMostMove = currentMove;
14744 HistorySet(parseList, backwardMostMove, forwardMostMove,
14752 if (appData.noChessProgram) return;
14753 switch (gameMode) {
14754 case MachinePlaysWhite:
14755 if (WhiteOnMove(forwardMostMove)) {
14756 DisplayError(_("Wait until your turn"), 0);
14760 case BeginningOfGame:
14761 case MachinePlaysBlack:
14762 if (!WhiteOnMove(forwardMostMove)) {
14763 DisplayError(_("Wait until your turn"), 0);
14768 DisplayError(_("No hint available"), 0);
14771 SendToProgram("hint\n", &first);
14772 hintRequested = TRUE;
14778 if (appData.noChessProgram) return;
14779 switch (gameMode) {
14780 case MachinePlaysWhite:
14781 if (WhiteOnMove(forwardMostMove)) {
14782 DisplayError(_("Wait until your turn"), 0);
14786 case BeginningOfGame:
14787 case MachinePlaysBlack:
14788 if (!WhiteOnMove(forwardMostMove)) {
14789 DisplayError(_("Wait until your turn"), 0);
14794 EditPositionDone(TRUE);
14796 case TwoMachinesPlay:
14801 SendToProgram("bk\n", &first);
14802 bookOutput[0] = NULLCHAR;
14803 bookRequested = TRUE;
14809 char *tags = PGNTags(&gameInfo);
14810 TagsPopUp(tags, CmailMsg());
14814 /* end button procedures */
14817 PrintPosition (FILE *fp, int move)
14821 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14822 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14823 char c = PieceToChar(boards[move][i][j]);
14824 fputc(c == 'x' ? '.' : c, fp);
14825 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14828 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14829 fprintf(fp, "white to play\n");
14831 fprintf(fp, "black to play\n");
14835 PrintOpponents (FILE *fp)
14837 if (gameInfo.white != NULL) {
14838 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14844 /* Find last component of program's own name, using some heuristics */
14846 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14849 int local = (strcmp(host, "localhost") == 0);
14850 while (!local && (p = strchr(prog, ';')) != NULL) {
14852 while (*p == ' ') p++;
14855 if (*prog == '"' || *prog == '\'') {
14856 q = strchr(prog + 1, *prog);
14858 q = strchr(prog, ' ');
14860 if (q == NULL) q = prog + strlen(prog);
14862 while (p >= prog && *p != '/' && *p != '\\') p--;
14864 if(p == prog && *p == '"') p++;
14866 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14867 memcpy(buf, p, q - p);
14868 buf[q - p] = NULLCHAR;
14876 TimeControlTagValue ()
14879 if (!appData.clockMode) {
14880 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14881 } else if (movesPerSession > 0) {
14882 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14883 } else if (timeIncrement == 0) {
14884 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14886 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14888 return StrSave(buf);
14894 /* This routine is used only for certain modes */
14895 VariantClass v = gameInfo.variant;
14896 ChessMove r = GameUnfinished;
14899 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14900 r = gameInfo.result;
14901 p = gameInfo.resultDetails;
14902 gameInfo.resultDetails = NULL;
14904 ClearGameInfo(&gameInfo);
14905 gameInfo.variant = v;
14907 switch (gameMode) {
14908 case MachinePlaysWhite:
14909 gameInfo.event = StrSave( appData.pgnEventHeader );
14910 gameInfo.site = StrSave(HostName());
14911 gameInfo.date = PGNDate();
14912 gameInfo.round = StrSave("-");
14913 gameInfo.white = StrSave(first.tidy);
14914 gameInfo.black = StrSave(UserName());
14915 gameInfo.timeControl = TimeControlTagValue();
14918 case MachinePlaysBlack:
14919 gameInfo.event = StrSave( appData.pgnEventHeader );
14920 gameInfo.site = StrSave(HostName());
14921 gameInfo.date = PGNDate();
14922 gameInfo.round = StrSave("-");
14923 gameInfo.white = StrSave(UserName());
14924 gameInfo.black = StrSave(first.tidy);
14925 gameInfo.timeControl = TimeControlTagValue();
14928 case TwoMachinesPlay:
14929 gameInfo.event = StrSave( appData.pgnEventHeader );
14930 gameInfo.site = StrSave(HostName());
14931 gameInfo.date = PGNDate();
14934 snprintf(buf, MSG_SIZ, "%d", roundNr);
14935 gameInfo.round = StrSave(buf);
14937 gameInfo.round = StrSave("-");
14939 if (first.twoMachinesColor[0] == 'w') {
14940 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14941 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14943 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14944 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14946 gameInfo.timeControl = TimeControlTagValue();
14950 gameInfo.event = StrSave("Edited game");
14951 gameInfo.site = StrSave(HostName());
14952 gameInfo.date = PGNDate();
14953 gameInfo.round = StrSave("-");
14954 gameInfo.white = StrSave("-");
14955 gameInfo.black = StrSave("-");
14956 gameInfo.result = r;
14957 gameInfo.resultDetails = p;
14961 gameInfo.event = StrSave("Edited position");
14962 gameInfo.site = StrSave(HostName());
14963 gameInfo.date = PGNDate();
14964 gameInfo.round = StrSave("-");
14965 gameInfo.white = StrSave("-");
14966 gameInfo.black = StrSave("-");
14969 case IcsPlayingWhite:
14970 case IcsPlayingBlack:
14975 case PlayFromGameFile:
14976 gameInfo.event = StrSave("Game from non-PGN file");
14977 gameInfo.site = StrSave(HostName());
14978 gameInfo.date = PGNDate();
14979 gameInfo.round = StrSave("-");
14980 gameInfo.white = StrSave("?");
14981 gameInfo.black = StrSave("?");
14990 ReplaceComment (int index, char *text)
14996 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14997 pvInfoList[index-1].depth == len &&
14998 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14999 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15000 while (*text == '\n') text++;
15001 len = strlen(text);
15002 while (len > 0 && text[len - 1] == '\n') len--;
15004 if (commentList[index] != NULL)
15005 free(commentList[index]);
15008 commentList[index] = NULL;
15011 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15012 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15013 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15014 commentList[index] = (char *) malloc(len + 2);
15015 strncpy(commentList[index], text, len);
15016 commentList[index][len] = '\n';
15017 commentList[index][len + 1] = NULLCHAR;
15019 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15021 commentList[index] = (char *) malloc(len + 7);
15022 safeStrCpy(commentList[index], "{\n", 3);
15023 safeStrCpy(commentList[index]+2, text, len+1);
15024 commentList[index][len+2] = NULLCHAR;
15025 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15026 strcat(commentList[index], "\n}\n");
15031 CrushCRs (char *text)
15039 if (ch == '\r') continue;
15041 } while (ch != '\0');
15045 AppendComment (int index, char *text, Boolean addBraces)
15046 /* addBraces tells if we should add {} */
15051 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15052 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15055 while (*text == '\n') text++;
15056 len = strlen(text);
15057 while (len > 0 && text[len - 1] == '\n') len--;
15058 text[len] = NULLCHAR;
15060 if (len == 0) return;
15062 if (commentList[index] != NULL) {
15063 Boolean addClosingBrace = addBraces;
15064 old = commentList[index];
15065 oldlen = strlen(old);
15066 while(commentList[index][oldlen-1] == '\n')
15067 commentList[index][--oldlen] = NULLCHAR;
15068 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15069 safeStrCpy(commentList[index], old, oldlen + len + 6);
15071 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15072 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15073 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15074 while (*text == '\n') { text++; len--; }
15075 commentList[index][--oldlen] = NULLCHAR;
15077 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15078 else strcat(commentList[index], "\n");
15079 strcat(commentList[index], text);
15080 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15081 else strcat(commentList[index], "\n");
15083 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15085 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15086 else commentList[index][0] = NULLCHAR;
15087 strcat(commentList[index], text);
15088 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15089 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15094 FindStr (char * text, char * sub_text)
15096 char * result = strstr( text, sub_text );
15098 if( result != NULL ) {
15099 result += strlen( sub_text );
15105 /* [AS] Try to extract PV info from PGN comment */
15106 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15108 GetInfoFromComment (int index, char * text)
15110 char * sep = text, *p;
15112 if( text != NULL && index > 0 ) {
15115 int time = -1, sec = 0, deci;
15116 char * s_eval = FindStr( text, "[%eval " );
15117 char * s_emt = FindStr( text, "[%emt " );
15119 if( s_eval != NULL || s_emt != NULL ) {
15123 if( s_eval != NULL ) {
15124 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15128 if( delim != ']' ) {
15133 if( s_emt != NULL ) {
15138 /* We expect something like: [+|-]nnn.nn/dd */
15141 if(*text != '{') return text; // [HGM] braces: must be normal comment
15143 sep = strchr( text, '/' );
15144 if( sep == NULL || sep < (text+4) ) {
15149 if(p[1] == '(') { // comment starts with PV
15150 p = strchr(p, ')'); // locate end of PV
15151 if(p == NULL || sep < p+5) return text;
15152 // at this point we have something like "{(.*) +0.23/6 ..."
15153 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15154 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15155 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15157 time = -1; sec = -1; deci = -1;
15158 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15159 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15160 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15161 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15165 if( score_lo < 0 || score_lo >= 100 ) {
15169 if(sec >= 0) time = 600*time + 10*sec; else
15170 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15172 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15174 /* [HGM] PV time: now locate end of PV info */
15175 while( *++sep >= '0' && *sep <= '9'); // strip depth
15177 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15179 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15181 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15182 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15193 pvInfoList[index-1].depth = depth;
15194 pvInfoList[index-1].score = score;
15195 pvInfoList[index-1].time = 10*time; // centi-sec
15196 if(*sep == '}') *sep = 0; else *--sep = '{';
15197 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15203 SendToProgram (char *message, ChessProgramState *cps)
15205 int count, outCount, error;
15208 if (cps->pr == NoProc) return;
15211 if (appData.debugMode) {
15214 fprintf(debugFP, "%ld >%-6s: %s",
15215 SubtractTimeMarks(&now, &programStartTime),
15216 cps->which, message);
15218 fprintf(serverFP, "%ld >%-6s: %s",
15219 SubtractTimeMarks(&now, &programStartTime),
15220 cps->which, message), fflush(serverFP);
15223 count = strlen(message);
15224 outCount = OutputToProcess(cps->pr, message, count, &error);
15225 if (outCount < count && !exiting
15226 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15227 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15228 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15229 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15230 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15231 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15232 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15233 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15235 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15236 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15237 gameInfo.result = res;
15239 gameInfo.resultDetails = StrSave(buf);
15241 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15242 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15247 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15251 ChessProgramState *cps = (ChessProgramState *)closure;
15253 if (isr != cps->isr) return; /* Killed intentionally */
15256 RemoveInputSource(cps->isr);
15257 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15258 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15259 _(cps->which), cps->program);
15260 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15261 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15262 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15263 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15264 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15266 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15267 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15268 gameInfo.result = res;
15270 gameInfo.resultDetails = StrSave(buf);
15272 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15273 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15275 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15276 _(cps->which), cps->program);
15277 RemoveInputSource(cps->isr);
15279 /* [AS] Program is misbehaving badly... kill it */
15280 if( count == -2 ) {
15281 DestroyChildProcess( cps->pr, 9 );
15285 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15290 if ((end_str = strchr(message, '\r')) != NULL)
15291 *end_str = NULLCHAR;
15292 if ((end_str = strchr(message, '\n')) != NULL)
15293 *end_str = NULLCHAR;
15295 if (appData.debugMode) {
15296 TimeMark now; int print = 1;
15297 char *quote = ""; char c; int i;
15299 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15300 char start = message[0];
15301 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15302 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15303 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15304 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15305 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15306 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15307 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15308 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15309 sscanf(message, "hint: %c", &c)!=1 &&
15310 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15311 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15312 print = (appData.engineComments >= 2);
15314 message[0] = start; // restore original message
15318 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15319 SubtractTimeMarks(&now, &programStartTime), cps->which,
15323 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15324 SubtractTimeMarks(&now, &programStartTime), cps->which,
15326 message), fflush(serverFP);
15330 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15331 if (appData.icsEngineAnalyze) {
15332 if (strstr(message, "whisper") != NULL ||
15333 strstr(message, "kibitz") != NULL ||
15334 strstr(message, "tellics") != NULL) return;
15337 HandleMachineMove(message, cps);
15342 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15347 if( timeControl_2 > 0 ) {
15348 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15349 tc = timeControl_2;
15352 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15353 inc /= cps->timeOdds;
15354 st /= cps->timeOdds;
15356 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15359 /* Set exact time per move, normally using st command */
15360 if (cps->stKludge) {
15361 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15363 if (seconds == 0) {
15364 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15366 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15369 snprintf(buf, MSG_SIZ, "st %d\n", st);
15372 /* Set conventional or incremental time control, using level command */
15373 if (seconds == 0) {
15374 /* Note old gnuchess bug -- minutes:seconds used to not work.
15375 Fixed in later versions, but still avoid :seconds
15376 when seconds is 0. */
15377 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15379 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15380 seconds, inc/1000.);
15383 SendToProgram(buf, cps);
15385 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15386 /* Orthogonally, limit search to given depth */
15388 if (cps->sdKludge) {
15389 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15391 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15393 SendToProgram(buf, cps);
15396 if(cps->nps >= 0) { /* [HGM] nps */
15397 if(cps->supportsNPS == FALSE)
15398 cps->nps = -1; // don't use if engine explicitly says not supported!
15400 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15401 SendToProgram(buf, cps);
15406 ChessProgramState *
15408 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15410 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15411 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15417 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15419 char message[MSG_SIZ];
15422 /* Note: this routine must be called when the clocks are stopped
15423 or when they have *just* been set or switched; otherwise
15424 it will be off by the time since the current tick started.
15426 if (machineWhite) {
15427 time = whiteTimeRemaining / 10;
15428 otime = blackTimeRemaining / 10;
15430 time = blackTimeRemaining / 10;
15431 otime = whiteTimeRemaining / 10;
15433 /* [HGM] translate opponent's time by time-odds factor */
15434 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15436 if (time <= 0) time = 1;
15437 if (otime <= 0) otime = 1;
15439 snprintf(message, MSG_SIZ, "time %ld\n", time);
15440 SendToProgram(message, cps);
15442 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15443 SendToProgram(message, cps);
15447 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15450 int len = strlen(name);
15453 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15455 sscanf(*p, "%d", &val);
15457 while (**p && **p != ' ')
15459 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15460 SendToProgram(buf, cps);
15467 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15470 int len = strlen(name);
15471 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15473 sscanf(*p, "%d", loc);
15474 while (**p && **p != ' ') (*p)++;
15475 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15476 SendToProgram(buf, cps);
15483 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15486 int len = strlen(name);
15487 if (strncmp((*p), name, len) == 0
15488 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15490 sscanf(*p, "%[^\"]", loc);
15491 while (**p && **p != '\"') (*p)++;
15492 if (**p == '\"') (*p)++;
15493 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15494 SendToProgram(buf, cps);
15501 ParseOption (Option *opt, ChessProgramState *cps)
15502 // [HGM] options: process the string that defines an engine option, and determine
15503 // name, type, default value, and allowed value range
15505 char *p, *q, buf[MSG_SIZ];
15506 int n, min = (-1)<<31, max = 1<<31, def;
15508 if(p = strstr(opt->name, " -spin ")) {
15509 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15510 if(max < min) max = min; // enforce consistency
15511 if(def < min) def = min;
15512 if(def > max) def = max;
15517 } else if((p = strstr(opt->name, " -slider "))) {
15518 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15519 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15520 if(max < min) max = min; // enforce consistency
15521 if(def < min) def = min;
15522 if(def > max) def = max;
15526 opt->type = Spin; // Slider;
15527 } else if((p = strstr(opt->name, " -string "))) {
15528 opt->textValue = p+9;
15529 opt->type = TextBox;
15530 } else if((p = strstr(opt->name, " -file "))) {
15531 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15532 opt->textValue = p+7;
15533 opt->type = FileName; // FileName;
15534 } else if((p = strstr(opt->name, " -path "))) {
15535 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15536 opt->textValue = p+7;
15537 opt->type = PathName; // PathName;
15538 } else if(p = strstr(opt->name, " -check ")) {
15539 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15540 opt->value = (def != 0);
15541 opt->type = CheckBox;
15542 } else if(p = strstr(opt->name, " -combo ")) {
15543 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15544 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15545 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15546 opt->value = n = 0;
15547 while(q = StrStr(q, " /// ")) {
15548 n++; *q = 0; // count choices, and null-terminate each of them
15550 if(*q == '*') { // remember default, which is marked with * prefix
15554 cps->comboList[cps->comboCnt++] = q;
15556 cps->comboList[cps->comboCnt++] = NULL;
15558 opt->type = ComboBox;
15559 } else if(p = strstr(opt->name, " -button")) {
15560 opt->type = Button;
15561 } else if(p = strstr(opt->name, " -save")) {
15562 opt->type = SaveButton;
15563 } else return FALSE;
15564 *p = 0; // terminate option name
15565 // now look if the command-line options define a setting for this engine option.
15566 if(cps->optionSettings && cps->optionSettings[0])
15567 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15568 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15569 snprintf(buf, MSG_SIZ, "option %s", p);
15570 if(p = strstr(buf, ",")) *p = 0;
15571 if(q = strchr(buf, '=')) switch(opt->type) {
15573 for(n=0; n<opt->max; n++)
15574 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15577 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15581 opt->value = atoi(q+1);
15586 SendToProgram(buf, cps);
15592 FeatureDone (ChessProgramState *cps, int val)
15594 DelayedEventCallback cb = GetDelayedEvent();
15595 if ((cb == InitBackEnd3 && cps == &first) ||
15596 (cb == SettingsMenuIfReady && cps == &second) ||
15597 (cb == LoadEngine) ||
15598 (cb == TwoMachinesEventIfReady)) {
15599 CancelDelayedEvent();
15600 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15602 cps->initDone = val;
15605 /* Parse feature command from engine */
15607 ParseFeatures (char *args, ChessProgramState *cps)
15615 while (*p == ' ') p++;
15616 if (*p == NULLCHAR) return;
15618 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15619 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15620 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15621 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15622 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15623 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15624 if (BoolFeature(&p, "reuse", &val, cps)) {
15625 /* Engine can disable reuse, but can't enable it if user said no */
15626 if (!val) cps->reuse = FALSE;
15629 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15630 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15631 if (gameMode == TwoMachinesPlay) {
15632 DisplayTwoMachinesTitle();
15638 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15639 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15640 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15641 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15642 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15643 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15644 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15645 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15646 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15647 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15648 if (IntFeature(&p, "done", &val, cps)) {
15649 FeatureDone(cps, val);
15652 /* Added by Tord: */
15653 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15654 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15655 /* End of additions by Tord */
15657 /* [HGM] added features: */
15658 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15659 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15660 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15661 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15662 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15663 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15664 if (StringFeature(&p, "option", buf, cps)) {
15665 FREE(cps->option[cps->nrOptions].name);
15666 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15667 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15668 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15669 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15670 SendToProgram(buf, cps);
15673 if(cps->nrOptions >= MAX_OPTIONS) {
15675 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15676 DisplayError(buf, 0);
15680 /* End of additions by HGM */
15682 /* unknown feature: complain and skip */
15684 while (*q && *q != '=') q++;
15685 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15686 SendToProgram(buf, cps);
15692 while (*p && *p != '\"') p++;
15693 if (*p == '\"') p++;
15695 while (*p && *p != ' ') p++;
15703 PeriodicUpdatesEvent (int newState)
15705 if (newState == appData.periodicUpdates)
15708 appData.periodicUpdates=newState;
15710 /* Display type changes, so update it now */
15711 // DisplayAnalysis();
15713 /* Get the ball rolling again... */
15715 AnalysisPeriodicEvent(1);
15716 StartAnalysisClock();
15721 PonderNextMoveEvent (int newState)
15723 if (newState == appData.ponderNextMove) return;
15724 if (gameMode == EditPosition) EditPositionDone(TRUE);
15726 SendToProgram("hard\n", &first);
15727 if (gameMode == TwoMachinesPlay) {
15728 SendToProgram("hard\n", &second);
15731 SendToProgram("easy\n", &first);
15732 thinkOutput[0] = NULLCHAR;
15733 if (gameMode == TwoMachinesPlay) {
15734 SendToProgram("easy\n", &second);
15737 appData.ponderNextMove = newState;
15741 NewSettingEvent (int option, int *feature, char *command, int value)
15745 if (gameMode == EditPosition) EditPositionDone(TRUE);
15746 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15747 if(feature == NULL || *feature) SendToProgram(buf, &first);
15748 if (gameMode == TwoMachinesPlay) {
15749 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15754 ShowThinkingEvent ()
15755 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15757 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15758 int newState = appData.showThinking
15759 // [HGM] thinking: other features now need thinking output as well
15760 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15762 if (oldState == newState) return;
15763 oldState = newState;
15764 if (gameMode == EditPosition) EditPositionDone(TRUE);
15766 SendToProgram("post\n", &first);
15767 if (gameMode == TwoMachinesPlay) {
15768 SendToProgram("post\n", &second);
15771 SendToProgram("nopost\n", &first);
15772 thinkOutput[0] = NULLCHAR;
15773 if (gameMode == TwoMachinesPlay) {
15774 SendToProgram("nopost\n", &second);
15777 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15781 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15783 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15784 if (pr == NoProc) return;
15785 AskQuestion(title, question, replyPrefix, pr);
15789 TypeInEvent (char firstChar)
15791 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15792 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15793 gameMode == AnalyzeMode || gameMode == EditGame ||
15794 gameMode == EditPosition || gameMode == IcsExamining ||
15795 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15796 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15797 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15798 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15799 gameMode == Training) PopUpMoveDialog(firstChar);
15803 TypeInDoneEvent (char *move)
15806 int n, fromX, fromY, toX, toY;
15808 ChessMove moveType;
15811 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15812 EditPositionPasteFEN(move);
15815 // [HGM] movenum: allow move number to be typed in any mode
15816 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15820 // undocumented kludge: allow command-line option to be typed in!
15821 // (potentially fatal, and does not implement the effect of the option.)
15822 // should only be used for options that are values on which future decisions will be made,
15823 // and definitely not on options that would be used during initialization.
15824 if(strstr(move, "!!! -") == move) {
15825 ParseArgsFromString(move+4);
15829 if (gameMode != EditGame && currentMove != forwardMostMove &&
15830 gameMode != Training) {
15831 DisplayMoveError(_("Displayed move is not current"));
15833 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15834 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15835 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15836 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15837 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15838 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15840 DisplayMoveError(_("Could not parse move"));
15846 DisplayMove (int moveNumber)
15848 char message[MSG_SIZ];
15850 char cpThinkOutput[MSG_SIZ];
15852 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15854 if (moveNumber == forwardMostMove - 1 ||
15855 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15857 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15859 if (strchr(cpThinkOutput, '\n')) {
15860 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15863 *cpThinkOutput = NULLCHAR;
15866 /* [AS] Hide thinking from human user */
15867 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15868 *cpThinkOutput = NULLCHAR;
15869 if( thinkOutput[0] != NULLCHAR ) {
15872 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15873 cpThinkOutput[i] = '.';
15875 cpThinkOutput[i] = NULLCHAR;
15876 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15880 if (moveNumber == forwardMostMove - 1 &&
15881 gameInfo.resultDetails != NULL) {
15882 if (gameInfo.resultDetails[0] == NULLCHAR) {
15883 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15885 snprintf(res, MSG_SIZ, " {%s} %s",
15886 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15892 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15893 DisplayMessage(res, cpThinkOutput);
15895 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15896 WhiteOnMove(moveNumber) ? " " : ".. ",
15897 parseList[moveNumber], res);
15898 DisplayMessage(message, cpThinkOutput);
15903 DisplayComment (int moveNumber, char *text)
15905 char title[MSG_SIZ];
15907 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15908 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15910 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15911 WhiteOnMove(moveNumber) ? " " : ".. ",
15912 parseList[moveNumber]);
15914 if (text != NULL && (appData.autoDisplayComment || commentUp))
15915 CommentPopUp(title, text);
15918 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15919 * might be busy thinking or pondering. It can be omitted if your
15920 * gnuchess is configured to stop thinking immediately on any user
15921 * input. However, that gnuchess feature depends on the FIONREAD
15922 * ioctl, which does not work properly on some flavors of Unix.
15925 Attention (ChessProgramState *cps)
15928 if (!cps->useSigint) return;
15929 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15930 switch (gameMode) {
15931 case MachinePlaysWhite:
15932 case MachinePlaysBlack:
15933 case TwoMachinesPlay:
15934 case IcsPlayingWhite:
15935 case IcsPlayingBlack:
15938 /* Skip if we know it isn't thinking */
15939 if (!cps->maybeThinking) return;
15940 if (appData.debugMode)
15941 fprintf(debugFP, "Interrupting %s\n", cps->which);
15942 InterruptChildProcess(cps->pr);
15943 cps->maybeThinking = FALSE;
15948 #endif /*ATTENTION*/
15954 if (whiteTimeRemaining <= 0) {
15957 if (appData.icsActive) {
15958 if (appData.autoCallFlag &&
15959 gameMode == IcsPlayingBlack && !blackFlag) {
15960 SendToICS(ics_prefix);
15961 SendToICS("flag\n");
15965 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15967 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15968 if (appData.autoCallFlag) {
15969 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15976 if (blackTimeRemaining <= 0) {
15979 if (appData.icsActive) {
15980 if (appData.autoCallFlag &&
15981 gameMode == IcsPlayingWhite && !whiteFlag) {
15982 SendToICS(ics_prefix);
15983 SendToICS("flag\n");
15987 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15989 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15990 if (appData.autoCallFlag) {
15991 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16002 CheckTimeControl ()
16004 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16005 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16008 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16010 if ( !WhiteOnMove(forwardMostMove) ) {
16011 /* White made time control */
16012 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16013 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16014 /* [HGM] time odds: correct new time quota for time odds! */
16015 / WhitePlayer()->timeOdds;
16016 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16018 lastBlack -= blackTimeRemaining;
16019 /* Black made time control */
16020 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16021 / WhitePlayer()->other->timeOdds;
16022 lastWhite = whiteTimeRemaining;
16027 DisplayBothClocks ()
16029 int wom = gameMode == EditPosition ?
16030 !blackPlaysFirst : WhiteOnMove(currentMove);
16031 DisplayWhiteClock(whiteTimeRemaining, wom);
16032 DisplayBlackClock(blackTimeRemaining, !wom);
16036 /* Timekeeping seems to be a portability nightmare. I think everyone
16037 has ftime(), but I'm really not sure, so I'm including some ifdefs
16038 to use other calls if you don't. Clocks will be less accurate if
16039 you have neither ftime nor gettimeofday.
16042 /* VS 2008 requires the #include outside of the function */
16043 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16044 #include <sys/timeb.h>
16047 /* Get the current time as a TimeMark */
16049 GetTimeMark (TimeMark *tm)
16051 #if HAVE_GETTIMEOFDAY
16053 struct timeval timeVal;
16054 struct timezone timeZone;
16056 gettimeofday(&timeVal, &timeZone);
16057 tm->sec = (long) timeVal.tv_sec;
16058 tm->ms = (int) (timeVal.tv_usec / 1000L);
16060 #else /*!HAVE_GETTIMEOFDAY*/
16063 // include <sys/timeb.h> / moved to just above start of function
16064 struct timeb timeB;
16067 tm->sec = (long) timeB.time;
16068 tm->ms = (int) timeB.millitm;
16070 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16071 tm->sec = (long) time(NULL);
16077 /* Return the difference in milliseconds between two
16078 time marks. We assume the difference will fit in a long!
16081 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16083 return 1000L*(tm2->sec - tm1->sec) +
16084 (long) (tm2->ms - tm1->ms);
16089 * Code to manage the game clocks.
16091 * In tournament play, black starts the clock and then white makes a move.
16092 * We give the human user a slight advantage if he is playing white---the
16093 * clocks don't run until he makes his first move, so it takes zero time.
16094 * Also, we don't account for network lag, so we could get out of sync
16095 * with GNU Chess's clock -- but then, referees are always right.
16098 static TimeMark tickStartTM;
16099 static long intendedTickLength;
16102 NextTickLength (long timeRemaining)
16104 long nominalTickLength, nextTickLength;
16106 if (timeRemaining > 0L && timeRemaining <= 10000L)
16107 nominalTickLength = 100L;
16109 nominalTickLength = 1000L;
16110 nextTickLength = timeRemaining % nominalTickLength;
16111 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16113 return nextTickLength;
16116 /* Adjust clock one minute up or down */
16118 AdjustClock (Boolean which, int dir)
16120 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16121 if(which) blackTimeRemaining += 60000*dir;
16122 else whiteTimeRemaining += 60000*dir;
16123 DisplayBothClocks();
16124 adjustedClock = TRUE;
16127 /* Stop clocks and reset to a fresh time control */
16131 (void) StopClockTimer();
16132 if (appData.icsActive) {
16133 whiteTimeRemaining = blackTimeRemaining = 0;
16134 } else if (searchTime) {
16135 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16136 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16137 } else { /* [HGM] correct new time quote for time odds */
16138 whiteTC = blackTC = fullTimeControlString;
16139 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16140 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16142 if (whiteFlag || blackFlag) {
16144 whiteFlag = blackFlag = FALSE;
16146 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16147 DisplayBothClocks();
16148 adjustedClock = FALSE;
16151 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16153 /* Decrement running clock by amount of time that has passed */
16157 long timeRemaining;
16158 long lastTickLength, fudge;
16161 if (!appData.clockMode) return;
16162 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16166 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16168 /* Fudge if we woke up a little too soon */
16169 fudge = intendedTickLength - lastTickLength;
16170 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16172 if (WhiteOnMove(forwardMostMove)) {
16173 if(whiteNPS >= 0) lastTickLength = 0;
16174 timeRemaining = whiteTimeRemaining -= lastTickLength;
16175 if(timeRemaining < 0 && !appData.icsActive) {
16176 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16177 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16178 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16179 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16182 DisplayWhiteClock(whiteTimeRemaining - fudge,
16183 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16185 if(blackNPS >= 0) lastTickLength = 0;
16186 timeRemaining = blackTimeRemaining -= lastTickLength;
16187 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16188 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16190 blackStartMove = forwardMostMove;
16191 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16194 DisplayBlackClock(blackTimeRemaining - fudge,
16195 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16197 if (CheckFlags()) return;
16200 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16201 StartClockTimer(intendedTickLength);
16203 /* if the time remaining has fallen below the alarm threshold, sound the
16204 * alarm. if the alarm has sounded and (due to a takeback or time control
16205 * with increment) the time remaining has increased to a level above the
16206 * threshold, reset the alarm so it can sound again.
16209 if (appData.icsActive && appData.icsAlarm) {
16211 /* make sure we are dealing with the user's clock */
16212 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16213 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16216 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16217 alarmSounded = FALSE;
16218 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16220 alarmSounded = TRUE;
16226 /* A player has just moved, so stop the previously running
16227 clock and (if in clock mode) start the other one.
16228 We redisplay both clocks in case we're in ICS mode, because
16229 ICS gives us an update to both clocks after every move.
16230 Note that this routine is called *after* forwardMostMove
16231 is updated, so the last fractional tick must be subtracted
16232 from the color that is *not* on move now.
16235 SwitchClocks (int newMoveNr)
16237 long lastTickLength;
16239 int flagged = FALSE;
16243 if (StopClockTimer() && appData.clockMode) {
16244 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16245 if (!WhiteOnMove(forwardMostMove)) {
16246 if(blackNPS >= 0) lastTickLength = 0;
16247 blackTimeRemaining -= lastTickLength;
16248 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16249 // if(pvInfoList[forwardMostMove].time == -1)
16250 pvInfoList[forwardMostMove].time = // use GUI time
16251 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16253 if(whiteNPS >= 0) lastTickLength = 0;
16254 whiteTimeRemaining -= lastTickLength;
16255 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16256 // if(pvInfoList[forwardMostMove].time == -1)
16257 pvInfoList[forwardMostMove].time =
16258 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16260 flagged = CheckFlags();
16262 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16263 CheckTimeControl();
16265 if (flagged || !appData.clockMode) return;
16267 switch (gameMode) {
16268 case MachinePlaysBlack:
16269 case MachinePlaysWhite:
16270 case BeginningOfGame:
16271 if (pausing) return;
16275 case PlayFromGameFile:
16283 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16284 if(WhiteOnMove(forwardMostMove))
16285 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16286 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16290 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16291 whiteTimeRemaining : blackTimeRemaining);
16292 StartClockTimer(intendedTickLength);
16296 /* Stop both clocks */
16300 long lastTickLength;
16303 if (!StopClockTimer()) return;
16304 if (!appData.clockMode) return;
16308 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16309 if (WhiteOnMove(forwardMostMove)) {
16310 if(whiteNPS >= 0) lastTickLength = 0;
16311 whiteTimeRemaining -= lastTickLength;
16312 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16314 if(blackNPS >= 0) lastTickLength = 0;
16315 blackTimeRemaining -= lastTickLength;
16316 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16321 /* Start clock of player on move. Time may have been reset, so
16322 if clock is already running, stop and restart it. */
16326 (void) StopClockTimer(); /* in case it was running already */
16327 DisplayBothClocks();
16328 if (CheckFlags()) return;
16330 if (!appData.clockMode) return;
16331 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16333 GetTimeMark(&tickStartTM);
16334 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16335 whiteTimeRemaining : blackTimeRemaining);
16337 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16338 whiteNPS = blackNPS = -1;
16339 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16340 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16341 whiteNPS = first.nps;
16342 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16343 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16344 blackNPS = first.nps;
16345 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16346 whiteNPS = second.nps;
16347 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16348 blackNPS = second.nps;
16349 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16351 StartClockTimer(intendedTickLength);
16355 TimeString (long ms)
16357 long second, minute, hour, day;
16359 static char buf[32];
16361 if (ms > 0 && ms <= 9900) {
16362 /* convert milliseconds to tenths, rounding up */
16363 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16365 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16369 /* convert milliseconds to seconds, rounding up */
16370 /* use floating point to avoid strangeness of integer division
16371 with negative dividends on many machines */
16372 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16379 day = second / (60 * 60 * 24);
16380 second = second % (60 * 60 * 24);
16381 hour = second / (60 * 60);
16382 second = second % (60 * 60);
16383 minute = second / 60;
16384 second = second % 60;
16387 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16388 sign, day, hour, minute, second);
16390 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16392 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16399 * This is necessary because some C libraries aren't ANSI C compliant yet.
16402 StrStr (char *string, char *match)
16406 length = strlen(match);
16408 for (i = strlen(string) - length; i >= 0; i--, string++)
16409 if (!strncmp(match, string, length))
16416 StrCaseStr (char *string, char *match)
16420 length = strlen(match);
16422 for (i = strlen(string) - length; i >= 0; i--, string++) {
16423 for (j = 0; j < length; j++) {
16424 if (ToLower(match[j]) != ToLower(string[j]))
16427 if (j == length) return string;
16435 StrCaseCmp (char *s1, char *s2)
16440 c1 = ToLower(*s1++);
16441 c2 = ToLower(*s2++);
16442 if (c1 > c2) return 1;
16443 if (c1 < c2) return -1;
16444 if (c1 == NULLCHAR) return 0;
16452 return isupper(c) ? tolower(c) : c;
16459 return islower(c) ? toupper(c) : c;
16461 #endif /* !_amigados */
16468 if ((ret = (char *) malloc(strlen(s) + 1)))
16470 safeStrCpy(ret, s, strlen(s)+1);
16476 StrSavePtr (char *s, char **savePtr)
16481 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16482 safeStrCpy(*savePtr, s, strlen(s)+1);
16494 clock = time((time_t *)NULL);
16495 tm = localtime(&clock);
16496 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16497 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16498 return StrSave(buf);
16503 PositionToFEN (int move, char *overrideCastling)
16505 int i, j, fromX, fromY, toX, toY;
16512 whiteToPlay = (gameMode == EditPosition) ?
16513 !blackPlaysFirst : (move % 2 == 0);
16516 /* Piece placement data */
16517 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16518 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16520 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16521 if (boards[move][i][j] == EmptySquare) {
16523 } else { ChessSquare piece = boards[move][i][j];
16524 if (emptycount > 0) {
16525 if(emptycount<10) /* [HGM] can be >= 10 */
16526 *p++ = '0' + emptycount;
16527 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16530 if(PieceToChar(piece) == '+') {
16531 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16533 piece = (ChessSquare)(DEMOTED piece);
16535 *p++ = PieceToChar(piece);
16537 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16538 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16543 if (emptycount > 0) {
16544 if(emptycount<10) /* [HGM] can be >= 10 */
16545 *p++ = '0' + emptycount;
16546 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16553 /* [HGM] print Crazyhouse or Shogi holdings */
16554 if( gameInfo.holdingsWidth ) {
16555 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16557 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16558 piece = boards[move][i][BOARD_WIDTH-1];
16559 if( piece != EmptySquare )
16560 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16561 *p++ = PieceToChar(piece);
16563 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16564 piece = boards[move][BOARD_HEIGHT-i-1][0];
16565 if( piece != EmptySquare )
16566 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16567 *p++ = PieceToChar(piece);
16570 if( q == p ) *p++ = '-';
16576 *p++ = whiteToPlay ? 'w' : 'b';
16579 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16580 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16582 if(nrCastlingRights) {
16584 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16585 /* [HGM] write directly from rights */
16586 if(boards[move][CASTLING][2] != NoRights &&
16587 boards[move][CASTLING][0] != NoRights )
16588 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16589 if(boards[move][CASTLING][2] != NoRights &&
16590 boards[move][CASTLING][1] != NoRights )
16591 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16592 if(boards[move][CASTLING][5] != NoRights &&
16593 boards[move][CASTLING][3] != NoRights )
16594 *p++ = boards[move][CASTLING][3] + AAA;
16595 if(boards[move][CASTLING][5] != NoRights &&
16596 boards[move][CASTLING][4] != NoRights )
16597 *p++ = boards[move][CASTLING][4] + AAA;
16600 /* [HGM] write true castling rights */
16601 if( nrCastlingRights == 6 ) {
16602 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16603 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16604 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16605 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16606 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16607 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16608 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16609 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16612 if (q == p) *p++ = '-'; /* No castling rights */
16616 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16617 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16618 /* En passant target square */
16619 if (move > backwardMostMove) {
16620 fromX = moveList[move - 1][0] - AAA;
16621 fromY = moveList[move - 1][1] - ONE;
16622 toX = moveList[move - 1][2] - AAA;
16623 toY = moveList[move - 1][3] - ONE;
16624 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16625 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16626 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16628 /* 2-square pawn move just happened */
16630 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16634 } else if(move == backwardMostMove) {
16635 // [HGM] perhaps we should always do it like this, and forget the above?
16636 if((signed char)boards[move][EP_STATUS] >= 0) {
16637 *p++ = boards[move][EP_STATUS] + AAA;
16638 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16649 /* [HGM] find reversible plies */
16650 { int i = 0, j=move;
16652 if (appData.debugMode) { int k;
16653 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16654 for(k=backwardMostMove; k<=forwardMostMove; k++)
16655 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16659 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16660 if( j == backwardMostMove ) i += initialRulePlies;
16661 sprintf(p, "%d ", i);
16662 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16664 /* Fullmove number */
16665 sprintf(p, "%d", (move / 2) + 1);
16667 return StrSave(buf);
16671 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16680 /* [HGM] by default clear Crazyhouse holdings, if present */
16681 if(gameInfo.holdingsWidth) {
16682 for(i=0; i<BOARD_HEIGHT; i++) {
16683 board[i][0] = EmptySquare; /* black holdings */
16684 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16685 board[i][1] = (ChessSquare) 0; /* black counts */
16686 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16690 /* Piece placement data */
16691 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16694 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16695 if (*p == '/') p++;
16696 emptycount = gameInfo.boardWidth - j;
16697 while (emptycount--)
16698 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16700 #if(BOARD_FILES >= 10)
16701 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16702 p++; emptycount=10;
16703 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16704 while (emptycount--)
16705 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16707 } else if (isdigit(*p)) {
16708 emptycount = *p++ - '0';
16709 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16710 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16711 while (emptycount--)
16712 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16713 } else if (*p == '+' || isalpha(*p)) {
16714 if (j >= gameInfo.boardWidth) return FALSE;
16716 piece = CharToPiece(*++p);
16717 if(piece == EmptySquare) return FALSE; /* unknown piece */
16718 piece = (ChessSquare) (PROMOTED piece ); p++;
16719 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16720 } else piece = CharToPiece(*p++);
16722 if(piece==EmptySquare) return FALSE; /* unknown piece */
16723 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16724 piece = (ChessSquare) (PROMOTED piece);
16725 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16728 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16734 while (*p == '/' || *p == ' ') p++;
16736 /* [HGM] look for Crazyhouse holdings here */
16737 while(*p==' ') p++;
16738 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16740 if(*p == '-' ) p++; /* empty holdings */ else {
16741 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16742 /* if we would allow FEN reading to set board size, we would */
16743 /* have to add holdings and shift the board read so far here */
16744 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16746 if((int) piece >= (int) BlackPawn ) {
16747 i = (int)piece - (int)BlackPawn;
16748 i = PieceToNumber((ChessSquare)i);
16749 if( i >= gameInfo.holdingsSize ) return FALSE;
16750 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16751 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16753 i = (int)piece - (int)WhitePawn;
16754 i = PieceToNumber((ChessSquare)i);
16755 if( i >= gameInfo.holdingsSize ) return FALSE;
16756 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16757 board[i][BOARD_WIDTH-2]++; /* black holdings */
16764 while(*p == ' ') p++;
16768 if(appData.colorNickNames) {
16769 if( c == appData.colorNickNames[0] ) c = 'w'; else
16770 if( c == appData.colorNickNames[1] ) c = 'b';
16774 *blackPlaysFirst = FALSE;
16777 *blackPlaysFirst = TRUE;
16783 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16784 /* return the extra info in global variiables */
16786 /* set defaults in case FEN is incomplete */
16787 board[EP_STATUS] = EP_UNKNOWN;
16788 for(i=0; i<nrCastlingRights; i++ ) {
16789 board[CASTLING][i] =
16790 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16791 } /* assume possible unless obviously impossible */
16792 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16793 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16794 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16795 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16796 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16797 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16798 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16799 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16802 while(*p==' ') p++;
16803 if(nrCastlingRights) {
16804 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16805 /* castling indicator present, so default becomes no castlings */
16806 for(i=0; i<nrCastlingRights; i++ ) {
16807 board[CASTLING][i] = NoRights;
16810 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16811 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16812 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16813 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16814 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16816 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16817 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16818 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16820 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16821 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16822 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16823 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16824 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16825 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16828 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16829 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16830 board[CASTLING][2] = whiteKingFile;
16833 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16834 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16835 board[CASTLING][2] = whiteKingFile;
16838 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16839 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16840 board[CASTLING][5] = blackKingFile;
16843 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16844 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16845 board[CASTLING][5] = blackKingFile;
16848 default: /* FRC castlings */
16849 if(c >= 'a') { /* black rights */
16850 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16851 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16852 if(i == BOARD_RGHT) break;
16853 board[CASTLING][5] = i;
16855 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16856 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16858 board[CASTLING][3] = c;
16860 board[CASTLING][4] = c;
16861 } else { /* white rights */
16862 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16863 if(board[0][i] == WhiteKing) break;
16864 if(i == BOARD_RGHT) break;
16865 board[CASTLING][2] = i;
16866 c -= AAA - 'a' + 'A';
16867 if(board[0][c] >= WhiteKing) break;
16869 board[CASTLING][0] = c;
16871 board[CASTLING][1] = c;
16875 for(i=0; i<nrCastlingRights; i++)
16876 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16877 if (appData.debugMode) {
16878 fprintf(debugFP, "FEN castling rights:");
16879 for(i=0; i<nrCastlingRights; i++)
16880 fprintf(debugFP, " %d", board[CASTLING][i]);
16881 fprintf(debugFP, "\n");
16884 while(*p==' ') p++;
16887 /* read e.p. field in games that know e.p. capture */
16888 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16889 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16891 p++; board[EP_STATUS] = EP_NONE;
16893 char c = *p++ - AAA;
16895 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16896 if(*p >= '0' && *p <='9') p++;
16897 board[EP_STATUS] = c;
16902 if(sscanf(p, "%d", &i) == 1) {
16903 FENrulePlies = i; /* 50-move ply counter */
16904 /* (The move number is still ignored) */
16911 EditPositionPasteFEN (char *fen)
16914 Board initial_position;
16916 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16917 DisplayError(_("Bad FEN position in clipboard"), 0);
16920 int savedBlackPlaysFirst = blackPlaysFirst;
16921 EditPositionEvent();
16922 blackPlaysFirst = savedBlackPlaysFirst;
16923 CopyBoard(boards[0], initial_position);
16924 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16925 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16926 DisplayBothClocks();
16927 DrawPosition(FALSE, boards[currentMove]);
16932 static char cseq[12] = "\\ ";
16935 set_cont_sequence (char *new_seq)
16940 // handle bad attempts to set the sequence
16942 return 0; // acceptable error - no debug
16944 len = strlen(new_seq);
16945 ret = (len > 0) && (len < sizeof(cseq));
16947 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16948 else if (appData.debugMode)
16949 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16954 reformat a source message so words don't cross the width boundary. internal
16955 newlines are not removed. returns the wrapped size (no null character unless
16956 included in source message). If dest is NULL, only calculate the size required
16957 for the dest buffer. lp argument indicats line position upon entry, and it's
16958 passed back upon exit.
16961 wrap (char *dest, char *src, int count, int width, int *lp)
16963 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16965 cseq_len = strlen(cseq);
16966 old_line = line = *lp;
16967 ansi = len = clen = 0;
16969 for (i=0; i < count; i++)
16971 if (src[i] == '\033')
16974 // if we hit the width, back up
16975 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16977 // store i & len in case the word is too long
16978 old_i = i, old_len = len;
16980 // find the end of the last word
16981 while (i && src[i] != ' ' && src[i] != '\n')
16987 // word too long? restore i & len before splitting it
16988 if ((old_i-i+clen) >= width)
16995 if (i && src[i-1] == ' ')
16998 if (src[i] != ' ' && src[i] != '\n')
17005 // now append the newline and continuation sequence
17010 strncpy(dest+len, cseq, cseq_len);
17018 dest[len] = src[i];
17022 if (src[i] == '\n')
17027 if (dest && appData.debugMode)
17029 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17030 count, width, line, len, *lp);
17031 show_bytes(debugFP, src, count);
17032 fprintf(debugFP, "\ndest: ");
17033 show_bytes(debugFP, dest, len);
17034 fprintf(debugFP, "\n");
17036 *lp = dest ? line : old_line;
17041 // [HGM] vari: routines for shelving variations
17042 Boolean modeRestore = FALSE;
17045 PushInner (int firstMove, int lastMove)
17047 int i, j, nrMoves = lastMove - firstMove;
17049 // push current tail of game on stack
17050 savedResult[storedGames] = gameInfo.result;
17051 savedDetails[storedGames] = gameInfo.resultDetails;
17052 gameInfo.resultDetails = NULL;
17053 savedFirst[storedGames] = firstMove;
17054 savedLast [storedGames] = lastMove;
17055 savedFramePtr[storedGames] = framePtr;
17056 framePtr -= nrMoves; // reserve space for the boards
17057 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17058 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17059 for(j=0; j<MOVE_LEN; j++)
17060 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17061 for(j=0; j<2*MOVE_LEN; j++)
17062 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17063 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17064 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17065 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17066 pvInfoList[firstMove+i-1].depth = 0;
17067 commentList[framePtr+i] = commentList[firstMove+i];
17068 commentList[firstMove+i] = NULL;
17072 forwardMostMove = firstMove; // truncate game so we can start variation
17076 PushTail (int firstMove, int lastMove)
17078 if(appData.icsActive) { // only in local mode
17079 forwardMostMove = currentMove; // mimic old ICS behavior
17082 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17084 PushInner(firstMove, lastMove);
17085 if(storedGames == 1) GreyRevert(FALSE);
17086 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17090 PopInner (Boolean annotate)
17093 char buf[8000], moveBuf[20];
17095 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17096 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17097 nrMoves = savedLast[storedGames] - currentMove;
17100 if(!WhiteOnMove(currentMove))
17101 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17102 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17103 for(i=currentMove; i<forwardMostMove; i++) {
17105 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17106 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17107 strcat(buf, moveBuf);
17108 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17109 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17113 for(i=1; i<=nrMoves; i++) { // copy last variation back
17114 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17115 for(j=0; j<MOVE_LEN; j++)
17116 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17117 for(j=0; j<2*MOVE_LEN; j++)
17118 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17119 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17120 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17121 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17122 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17123 commentList[currentMove+i] = commentList[framePtr+i];
17124 commentList[framePtr+i] = NULL;
17126 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17127 framePtr = savedFramePtr[storedGames];
17128 gameInfo.result = savedResult[storedGames];
17129 if(gameInfo.resultDetails != NULL) {
17130 free(gameInfo.resultDetails);
17132 gameInfo.resultDetails = savedDetails[storedGames];
17133 forwardMostMove = currentMove + nrMoves;
17137 PopTail (Boolean annotate)
17139 if(appData.icsActive) return FALSE; // only in local mode
17140 if(!storedGames) return FALSE; // sanity
17141 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17143 PopInner(annotate);
17144 if(currentMove < forwardMostMove) ForwardEvent(); else
17145 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17147 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17153 { // remove all shelved variations
17155 for(i=0; i<storedGames; i++) {
17156 if(savedDetails[i])
17157 free(savedDetails[i]);
17158 savedDetails[i] = NULL;
17160 for(i=framePtr; i<MAX_MOVES; i++) {
17161 if(commentList[i]) free(commentList[i]);
17162 commentList[i] = NULL;
17164 framePtr = MAX_MOVES-1;
17169 LoadVariation (int index, char *text)
17170 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17171 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17172 int level = 0, move;
17174 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17175 // first find outermost bracketing variation
17176 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17177 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17178 if(*p == '{') wait = '}'; else
17179 if(*p == '[') wait = ']'; else
17180 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17181 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17183 if(*p == wait) wait = NULLCHAR; // closing ]} found
17186 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17187 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17188 end[1] = NULLCHAR; // clip off comment beyond variation
17189 ToNrEvent(currentMove-1);
17190 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17191 // kludge: use ParsePV() to append variation to game
17192 move = currentMove;
17193 ParsePV(start, TRUE, TRUE);
17194 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17195 ClearPremoveHighlights();
17197 ToNrEvent(currentMove+1);