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);
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 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
960 FloatToFront(&appData.recentEngineList, buf);
962 ReplaceEngine(cps, i);
968 int matched, min, sec;
970 * Parse timeControl resource
972 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
973 appData.movesPerSession)) {
975 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
976 DisplayFatalError(buf, 0, 2);
980 * Parse searchTime resource
982 if (*appData.searchTime != NULLCHAR) {
983 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985 searchTime = min * 60;
986 } else if (matched == 2) {
987 searchTime = min * 60 + sec;
990 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
991 DisplayFatalError(buf, 0, 2);
1000 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1001 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1003 GetTimeMark(&programStartTime);
1004 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1005 appData.seedBase = random() + (random()<<15);
1006 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1008 ClearProgramStats();
1009 programStats.ok_to_send = 1;
1010 programStats.seen_stat = 0;
1013 * Initialize game list
1019 * Internet chess server status
1021 if (appData.icsActive) {
1022 appData.matchMode = FALSE;
1023 appData.matchGames = 0;
1025 appData.noChessProgram = !appData.zippyPlay;
1027 appData.zippyPlay = FALSE;
1028 appData.zippyTalk = FALSE;
1029 appData.noChessProgram = TRUE;
1031 if (*appData.icsHelper != NULLCHAR) {
1032 appData.useTelnet = TRUE;
1033 appData.telnetProgram = appData.icsHelper;
1036 appData.zippyTalk = appData.zippyPlay = FALSE;
1039 /* [AS] Initialize pv info list [HGM] and game state */
1043 for( i=0; i<=framePtr; i++ ) {
1044 pvInfoList[i].depth = -1;
1045 boards[i][EP_STATUS] = EP_NONE;
1046 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1052 /* [AS] Adjudication threshold */
1053 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1055 InitEngine(&first, 0);
1056 InitEngine(&second, 1);
1059 pairing.which = "pairing"; // pairing engine
1060 pairing.pr = NoProc;
1062 pairing.program = appData.pairingEngine;
1063 pairing.host = "localhost";
1066 if (appData.icsActive) {
1067 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1068 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1069 appData.clockMode = FALSE;
1070 first.sendTime = second.sendTime = 0;
1074 /* Override some settings from environment variables, for backward
1075 compatibility. Unfortunately it's not feasible to have the env
1076 vars just set defaults, at least in xboard. Ugh.
1078 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1083 if (!appData.icsActive) {
1087 /* Check for variants that are supported only in ICS mode,
1088 or not at all. Some that are accepted here nevertheless
1089 have bugs; see comments below.
1091 VariantClass variant = StringToVariant(appData.variant);
1093 case VariantBughouse: /* need four players and two boards */
1094 case VariantKriegspiel: /* need to hide pieces and move details */
1095 /* case VariantFischeRandom: (Fabien: moved below) */
1096 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1097 if( (len >= MSG_SIZ) && appData.debugMode )
1098 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1100 DisplayFatalError(buf, 0, 2);
1103 case VariantUnknown:
1104 case VariantLoadable:
1114 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1115 if( (len >= MSG_SIZ) && appData.debugMode )
1116 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1118 DisplayFatalError(buf, 0, 2);
1121 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1122 case VariantFairy: /* [HGM] TestLegality definitely off! */
1123 case VariantGothic: /* [HGM] should work */
1124 case VariantCapablanca: /* [HGM] should work */
1125 case VariantCourier: /* [HGM] initial forced moves not implemented */
1126 case VariantShogi: /* [HGM] could still mate with pawn drop */
1127 case VariantKnightmate: /* [HGM] should work */
1128 case VariantCylinder: /* [HGM] untested */
1129 case VariantFalcon: /* [HGM] untested */
1130 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1131 offboard interposition not understood */
1132 case VariantNormal: /* definitely works! */
1133 case VariantWildCastle: /* pieces not automatically shuffled */
1134 case VariantNoCastle: /* pieces not automatically shuffled */
1135 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1136 case VariantLosers: /* should work except for win condition,
1137 and doesn't know captures are mandatory */
1138 case VariantSuicide: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantGiveaway: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantTwoKings: /* should work */
1143 case VariantAtomic: /* should work except for win condition */
1144 case Variant3Check: /* should work except for win condition */
1145 case VariantShatranj: /* should work except for all win conditions */
1146 case VariantMakruk: /* should work except for draw countdown */
1147 case VariantBerolina: /* might work if TestLegality is off */
1148 case VariantCapaRandom: /* should work */
1149 case VariantJanus: /* should work */
1150 case VariantSuper: /* experimental */
1151 case VariantGreat: /* experimental, requires legality testing to be off */
1152 case VariantSChess: /* S-Chess, should work */
1153 case VariantGrand: /* should work */
1154 case VariantSpartan: /* should work */
1162 NextIntegerFromString (char ** str, long * value)
1167 while( *s == ' ' || *s == '\t' ) {
1173 if( *s >= '0' && *s <= '9' ) {
1174 while( *s >= '0' && *s <= '9' ) {
1175 *value = *value * 10 + (*s - '0');
1188 NextTimeControlFromString (char ** str, long * value)
1191 int result = NextIntegerFromString( str, &temp );
1194 *value = temp * 60; /* Minutes */
1195 if( **str == ':' ) {
1197 result = NextIntegerFromString( str, &temp );
1198 *value += temp; /* Seconds */
1206 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1207 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1208 int result = -1, type = 0; long temp, temp2;
1210 if(**str != ':') return -1; // old params remain in force!
1212 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1213 if( NextIntegerFromString( str, &temp ) ) return -1;
1214 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217 /* time only: incremental or sudden-death time control */
1218 if(**str == '+') { /* increment follows; read it */
1220 if(**str == '!') type = *(*str)++; // Bronstein TC
1221 if(result = NextIntegerFromString( str, &temp2)) return -1;
1222 *inc = temp2 * 1000;
1223 if(**str == '.') { // read fraction of increment
1224 char *start = ++(*str);
1225 if(result = NextIntegerFromString( str, &temp2)) return -1;
1227 while(start++ < *str) temp2 /= 10;
1231 *moves = 0; *tc = temp * 1000; *incType = type;
1235 (*str)++; /* classical time control */
1236 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1248 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1249 { /* [HGM] get time to add from the multi-session time-control string */
1250 int incType, moves=1; /* kludge to force reading of first session */
1251 long time, increment;
1254 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1256 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1257 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1258 if(movenr == -1) return time; /* last move before new session */
1259 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1260 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1261 if(!moves) return increment; /* current session is incremental */
1262 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1263 } while(movenr >= -1); /* try again for next session */
1265 return 0; // no new time quota on this move
1269 ParseTimeControl (char *tc, float ti, int mps)
1273 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1277 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1278 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1282 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1284 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1289 snprintf(buf, MSG_SIZ, ":%s", mytc);
1291 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1293 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1298 /* Parse second time control */
1301 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1309 timeControl_2 = tc2 * 1000;
1319 timeControl = tc1 * 1000;
1322 timeIncrement = ti * 1000; /* convert to ms */
1323 movesPerSession = 0;
1326 movesPerSession = mps;
1334 if (appData.debugMode) {
1335 fprintf(debugFP, "%s\n", programVersion);
1337 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1339 set_cont_sequence(appData.wrapContSeq);
1340 if (appData.matchGames > 0) {
1341 appData.matchMode = TRUE;
1342 } else if (appData.matchMode) {
1343 appData.matchGames = 1;
1345 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1346 appData.matchGames = appData.sameColorGames;
1347 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1348 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1349 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352 if (appData.noChessProgram || first.protocolVersion == 1) {
1355 /* kludge: allow timeout for initial "feature" commands */
1357 DisplayMessage("", _("Starting chess program"));
1358 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1363 CalculateIndex (int index, int gameNr)
1364 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1366 if(index > 0) return index; // fixed nmber
1367 if(index == 0) return 1;
1368 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1369 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1374 LoadGameOrPosition (int gameNr)
1375 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1376 if (*appData.loadGameFile != NULLCHAR) {
1377 if (!LoadGameFromFile(appData.loadGameFile,
1378 CalculateIndex(appData.loadGameIndex, gameNr),
1379 appData.loadGameFile, FALSE)) {
1380 DisplayFatalError(_("Bad game file"), 0, 1);
1383 } else if (*appData.loadPositionFile != NULLCHAR) {
1384 if (!LoadPositionFromFile(appData.loadPositionFile,
1385 CalculateIndex(appData.loadPositionIndex, gameNr),
1386 appData.loadPositionFile)) {
1387 DisplayFatalError(_("Bad position file"), 0, 1);
1395 ReserveGame (int gameNr, char resChar)
1397 FILE *tf = fopen(appData.tourneyFile, "r+");
1398 char *p, *q, c, buf[MSG_SIZ];
1399 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1400 safeStrCpy(buf, lastMsg, MSG_SIZ);
1401 DisplayMessage(_("Pick new game"), "");
1402 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1403 ParseArgsFromFile(tf);
1404 p = q = appData.results;
1405 if(appData.debugMode) {
1406 char *r = appData.participants;
1407 fprintf(debugFP, "results = '%s'\n", p);
1408 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1409 fprintf(debugFP, "\n");
1411 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1413 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1414 safeStrCpy(q, p, strlen(p) + 2);
1415 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1416 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1417 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1418 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421 fseek(tf, -(strlen(p)+4), SEEK_END);
1423 if(c != '"') // depending on DOS or Unix line endings we can be one off
1424 fseek(tf, -(strlen(p)+2), SEEK_END);
1425 else fseek(tf, -(strlen(p)+3), SEEK_END);
1426 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1427 DisplayMessage(buf, "");
1428 free(p); appData.results = q;
1429 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1430 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1431 int round = appData.defaultMatchGames * appData.tourneyType;
1432 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1433 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1434 UnloadEngine(&first); // next game belongs to other pairing;
1435 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1437 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1441 MatchEvent (int mode)
1442 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1444 if(matchMode) { // already in match mode: switch it off
1446 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449 // if(gameMode != BeginningOfGame) {
1450 // DisplayError(_("You can only start a match from the initial position."), 0);
1454 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1455 /* Set up machine vs. machine match */
1457 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1458 if(appData.tourneyFile[0]) {
1460 if(nextGame > appData.matchGames) {
1462 if(strchr(appData.results, '*') == NULL) {
1464 appData.tourneyCycles++;
1465 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1467 NextTourneyGame(-1, &dummy);
1469 if(nextGame <= appData.matchGames) {
1470 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1472 ScheduleDelayedEvent(NextMatchGame, 10000);
1477 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1478 DisplayError(buf, 0);
1479 appData.tourneyFile[0] = 0;
1483 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1484 DisplayFatalError(_("Can't have a match with no chess programs"),
1489 matchGame = roundNr = 1;
1490 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1494 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497 InitBackEnd3 P((void))
1499 GameMode initialMode;
1503 InitChessProgram(&first, startedFromSetupPosition);
1505 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1506 free(programVersion);
1507 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1508 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1509 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512 if (appData.icsActive) {
1514 /* [DM] Make a console window if needed [HGM] merged ifs */
1520 if (*appData.icsCommPort != NULLCHAR)
1521 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1522 appData.icsCommPort);
1524 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1525 appData.icsHost, appData.icsPort);
1527 if( (len >= MSG_SIZ) && appData.debugMode )
1528 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1530 DisplayFatalError(buf, err, 1);
1535 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1537 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1538 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1539 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1540 } else if (appData.noChessProgram) {
1546 if (*appData.cmailGameName != NULLCHAR) {
1548 OpenLoopback(&cmailPR);
1550 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1554 DisplayMessage("", "");
1555 if (StrCaseCmp(appData.initialMode, "") == 0) {
1556 initialMode = BeginningOfGame;
1557 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1558 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1559 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1560 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1564 initialMode = TwoMachinesPlay;
1565 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1566 initialMode = AnalyzeFile;
1567 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1568 initialMode = AnalyzeMode;
1569 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1570 initialMode = MachinePlaysWhite;
1571 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1572 initialMode = MachinePlaysBlack;
1573 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1574 initialMode = EditGame;
1575 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1576 initialMode = EditPosition;
1577 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1578 initialMode = Training;
1580 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1581 if( (len >= MSG_SIZ) && appData.debugMode )
1582 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584 DisplayFatalError(buf, 0, 2);
1588 if (appData.matchMode) {
1589 if(appData.tourneyFile[0]) { // start tourney from command line
1591 if(f = fopen(appData.tourneyFile, "r")) {
1592 ParseArgsFromFile(f); // make sure tourney parmeters re known
1594 appData.clockMode = TRUE;
1596 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599 } else if (*appData.cmailGameName != NULLCHAR) {
1600 /* Set up cmail mode */
1601 ReloadCmailMsgEvent(TRUE);
1603 /* Set up other modes */
1604 if (initialMode == AnalyzeFile) {
1605 if (*appData.loadGameFile == NULLCHAR) {
1606 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1610 if (*appData.loadGameFile != NULLCHAR) {
1611 (void) LoadGameFromFile(appData.loadGameFile,
1612 appData.loadGameIndex,
1613 appData.loadGameFile, TRUE);
1614 } else if (*appData.loadPositionFile != NULLCHAR) {
1615 (void) LoadPositionFromFile(appData.loadPositionFile,
1616 appData.loadPositionIndex,
1617 appData.loadPositionFile);
1618 /* [HGM] try to make self-starting even after FEN load */
1619 /* to allow automatic setup of fairy variants with wtm */
1620 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1621 gameMode = BeginningOfGame;
1622 setboardSpoiledMachineBlack = 1;
1624 /* [HGM] loadPos: make that every new game uses the setup */
1625 /* from file as long as we do not switch variant */
1626 if(!blackPlaysFirst) {
1627 startedFromPositionFile = TRUE;
1628 CopyBoard(filePosition, boards[0]);
1631 if (initialMode == AnalyzeMode) {
1632 if (appData.noChessProgram) {
1633 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636 if (appData.icsActive) {
1637 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1641 } else if (initialMode == AnalyzeFile) {
1642 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1643 ShowThinkingEvent();
1645 AnalysisPeriodicEvent(1);
1646 } else if (initialMode == MachinePlaysWhite) {
1647 if (appData.noChessProgram) {
1648 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1652 if (appData.icsActive) {
1653 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1657 MachineWhiteEvent();
1658 } else if (initialMode == MachinePlaysBlack) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1664 if (appData.icsActive) {
1665 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1669 MachineBlackEvent();
1670 } else if (initialMode == TwoMachinesPlay) {
1671 if (appData.noChessProgram) {
1672 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1676 if (appData.icsActive) {
1677 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1682 } else if (initialMode == EditGame) {
1684 } else if (initialMode == EditPosition) {
1685 EditPositionEvent();
1686 } else if (initialMode == Training) {
1687 if (*appData.loadGameFile == NULLCHAR) {
1688 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1697 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1699 DisplayBook(current+1);
1701 MoveHistorySet( movelist, first, last, current, pvInfoList );
1703 EvalGraphSet( first, last, current, pvInfoList );
1705 MakeEngineOutputTitle();
1709 * Establish will establish a contact to a remote host.port.
1710 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1711 * used to talk to the host.
1712 * Returns 0 if okay, error code if not.
1719 if (*appData.icsCommPort != NULLCHAR) {
1720 /* Talk to the host through a serial comm port */
1721 return OpenCommPort(appData.icsCommPort, &icsPR);
1723 } else if (*appData.gateway != NULLCHAR) {
1724 if (*appData.remoteShell == NULLCHAR) {
1725 /* Use the rcmd protocol to run telnet program on a gateway host */
1726 snprintf(buf, sizeof(buf), "%s %s %s",
1727 appData.telnetProgram, appData.icsHost, appData.icsPort);
1728 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731 /* Use the rsh program to run telnet program on a gateway host */
1732 if (*appData.remoteUser == NULLCHAR) {
1733 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1734 appData.gateway, appData.telnetProgram,
1735 appData.icsHost, appData.icsPort);
1737 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1738 appData.remoteShell, appData.gateway,
1739 appData.remoteUser, appData.telnetProgram,
1740 appData.icsHost, appData.icsPort);
1742 return StartChildProcess(buf, "", &icsPR);
1745 } else if (appData.useTelnet) {
1746 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749 /* TCP socket interface differs somewhat between
1750 Unix and NT; handle details in the front end.
1752 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1757 EscapeExpand (char *p, char *q)
1758 { // [HGM] initstring: routine to shape up string arguments
1759 while(*p++ = *q++) if(p[-1] == '\\')
1761 case 'n': p[-1] = '\n'; break;
1762 case 'r': p[-1] = '\r'; break;
1763 case 't': p[-1] = '\t'; break;
1764 case '\\': p[-1] = '\\'; break;
1765 case 0: *p = 0; return;
1766 default: p[-1] = q[-1]; break;
1771 show_bytes (FILE *fp, char *buf, int count)
1774 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1775 fprintf(fp, "\\%03o", *buf & 0xff);
1784 /* Returns an errno value */
1786 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1788 char buf[8192], *p, *q, *buflim;
1789 int left, newcount, outcount;
1791 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1792 *appData.gateway != NULLCHAR) {
1793 if (appData.debugMode) {
1794 fprintf(debugFP, ">ICS: ");
1795 show_bytes(debugFP, message, count);
1796 fprintf(debugFP, "\n");
1798 return OutputToProcess(pr, message, count, outError);
1801 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808 if (appData.debugMode) {
1809 fprintf(debugFP, ">ICS: ");
1810 show_bytes(debugFP, buf, newcount);
1811 fprintf(debugFP, "\n");
1813 outcount = OutputToProcess(pr, buf, newcount, outError);
1814 if (outcount < newcount) return -1; /* to be sure */
1821 } else if (((unsigned char) *p) == TN_IAC) {
1822 *q++ = (char) TN_IAC;
1829 if (appData.debugMode) {
1830 fprintf(debugFP, ">ICS: ");
1831 show_bytes(debugFP, buf, newcount);
1832 fprintf(debugFP, "\n");
1834 outcount = OutputToProcess(pr, buf, newcount, outError);
1835 if (outcount < newcount) return -1; /* to be sure */
1840 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1842 int outError, outCount;
1843 static int gotEof = 0;
1845 /* Pass data read from player on to ICS */
1848 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1849 if (outCount < count) {
1850 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852 } else if (count < 0) {
1853 RemoveInputSource(isr);
1854 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1855 } else if (gotEof++ > 0) {
1856 RemoveInputSource(isr);
1857 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1863 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1864 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1865 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1866 SendToICS("date\n");
1867 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 /* added routine for printf style output to ics */
1872 ics_printf (char *format, ...)
1874 char buffer[MSG_SIZ];
1877 va_start(args, format);
1878 vsnprintf(buffer, sizeof(buffer), format, args);
1879 buffer[sizeof(buffer)-1] = '\0';
1887 int count, outCount, outError;
1889 if (icsPR == NoProc) return;
1892 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1893 if (outCount < count) {
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 /* This is used for sending logon scripts to the ICS. Sending
1899 without a delay causes problems when using timestamp on ICC
1900 (at least on my machine). */
1902 SendToICSDelayed (char *s, long msdelay)
1904 int count, outCount, outError;
1906 if (icsPR == NoProc) return;
1909 if (appData.debugMode) {
1910 fprintf(debugFP, ">ICS: ");
1911 show_bytes(debugFP, s, count);
1912 fprintf(debugFP, "\n");
1914 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1916 if (outCount < count) {
1917 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1922 /* Remove all highlighting escape sequences in s
1923 Also deletes any suffix starting with '('
1926 StripHighlightAndTitle (char *s)
1928 static char retbuf[MSG_SIZ];
1931 while (*s != NULLCHAR) {
1932 while (*s == '\033') {
1933 while (*s != NULLCHAR && !isalpha(*s)) s++;
1934 if (*s != NULLCHAR) s++;
1936 while (*s != NULLCHAR && *s != '\033') {
1937 if (*s == '(' || *s == '[') {
1948 /* Remove all highlighting escape sequences in s */
1950 StripHighlight (char *s)
1952 static char retbuf[MSG_SIZ];
1955 while (*s != NULLCHAR) {
1956 while (*s == '\033') {
1957 while (*s != NULLCHAR && !isalpha(*s)) s++;
1958 if (*s != NULLCHAR) s++;
1960 while (*s != NULLCHAR && *s != '\033') {
1968 char *variantNames[] = VARIANT_NAMES;
1970 VariantName (VariantClass v)
1972 return variantNames[v];
1976 /* Identify a variant from the strings the chess servers use or the
1977 PGN Variant tag names we use. */
1979 StringToVariant (char *e)
1983 VariantClass v = VariantNormal;
1984 int i, found = FALSE;
1990 /* [HGM] skip over optional board-size prefixes */
1991 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1992 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1993 while( *e++ != '_');
1996 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2000 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2001 if (StrCaseStr(e, variantNames[i])) {
2002 v = (VariantClass) i;
2009 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2010 || StrCaseStr(e, "wild/fr")
2011 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2012 v = VariantFischeRandom;
2013 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2014 (i = 1, p = StrCaseStr(e, "w"))) {
2016 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2023 case 0: /* FICS only, actually */
2025 /* Castling legal even if K starts on d-file */
2026 v = VariantWildCastle;
2031 /* Castling illegal even if K & R happen to start in
2032 normal positions. */
2033 v = VariantNoCastle;
2046 /* Castling legal iff K & R start in normal positions */
2052 /* Special wilds for position setup; unclear what to do here */
2053 v = VariantLoadable;
2056 /* Bizarre ICC game */
2057 v = VariantTwoKings;
2060 v = VariantKriegspiel;
2066 v = VariantFischeRandom;
2069 v = VariantCrazyhouse;
2072 v = VariantBughouse;
2078 /* Not quite the same as FICS suicide! */
2079 v = VariantGiveaway;
2085 v = VariantShatranj;
2088 /* Temporary names for future ICC types. The name *will* change in
2089 the next xboard/WinBoard release after ICC defines it. */
2127 v = VariantCapablanca;
2130 v = VariantKnightmate;
2136 v = VariantCylinder;
2142 v = VariantCapaRandom;
2145 v = VariantBerolina;
2157 /* Found "wild" or "w" in the string but no number;
2158 must assume it's normal chess. */
2162 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2163 if( (len >= MSG_SIZ) && appData.debugMode )
2164 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2166 DisplayError(buf, 0);
2172 if (appData.debugMode) {
2173 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2174 e, wnum, VariantName(v));
2179 static int leftover_start = 0, leftover_len = 0;
2180 char star_match[STAR_MATCH_N][MSG_SIZ];
2182 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2183 advance *index beyond it, and set leftover_start to the new value of
2184 *index; else return FALSE. If pattern contains the character '*', it
2185 matches any sequence of characters not containing '\r', '\n', or the
2186 character following the '*' (if any), and the matched sequence(s) are
2187 copied into star_match.
2190 looking_at ( char *buf, int *index, char *pattern)
2192 char *bufp = &buf[*index], *patternp = pattern;
2194 char *matchp = star_match[0];
2197 if (*patternp == NULLCHAR) {
2198 *index = leftover_start = bufp - buf;
2202 if (*bufp == NULLCHAR) return FALSE;
2203 if (*patternp == '*') {
2204 if (*bufp == *(patternp + 1)) {
2206 matchp = star_match[++star_count];
2210 } else if (*bufp == '\n' || *bufp == '\r') {
2212 if (*patternp == NULLCHAR)
2217 *matchp++ = *bufp++;
2221 if (*patternp != *bufp) return FALSE;
2228 SendToPlayer (char *data, int length)
2230 int error, outCount;
2231 outCount = OutputToProcess(NoProc, data, length, &error);
2232 if (outCount < length) {
2233 DisplayFatalError(_("Error writing to display"), error, 1);
2238 PackHolding (char packed[], char *holding)
2248 switch (runlength) {
2259 sprintf(q, "%d", runlength);
2271 /* Telnet protocol requests from the front end */
2273 TelnetRequest (unsigned char ddww, unsigned char option)
2275 unsigned char msg[3];
2276 int outCount, outError;
2278 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2280 if (appData.debugMode) {
2281 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2297 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2306 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2314 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2316 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2323 if (!appData.icsActive) return;
2324 TelnetRequest(TN_DO, TN_ECHO);
2330 if (!appData.icsActive) return;
2331 TelnetRequest(TN_DONT, TN_ECHO);
2335 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2337 /* put the holdings sent to us by the server on the board holdings area */
2338 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2342 if(gameInfo.holdingsWidth < 2) return;
2343 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2344 return; // prevent overwriting by pre-board holdings
2346 if( (int)lowestPiece >= BlackPawn ) {
2349 holdingsStartRow = BOARD_HEIGHT-1;
2352 holdingsColumn = BOARD_WIDTH-1;
2353 countsColumn = BOARD_WIDTH-2;
2354 holdingsStartRow = 0;
2358 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2359 board[i][holdingsColumn] = EmptySquare;
2360 board[i][countsColumn] = (ChessSquare) 0;
2362 while( (p=*holdings++) != NULLCHAR ) {
2363 piece = CharToPiece( ToUpper(p) );
2364 if(piece == EmptySquare) continue;
2365 /*j = (int) piece - (int) WhitePawn;*/
2366 j = PieceToNumber(piece);
2367 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2368 if(j < 0) continue; /* should not happen */
2369 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2370 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2371 board[holdingsStartRow+j*direction][countsColumn]++;
2377 VariantSwitch (Board board, VariantClass newVariant)
2379 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2380 static Board oldBoard;
2382 startedFromPositionFile = FALSE;
2383 if(gameInfo.variant == newVariant) return;
2385 /* [HGM] This routine is called each time an assignment is made to
2386 * gameInfo.variant during a game, to make sure the board sizes
2387 * are set to match the new variant. If that means adding or deleting
2388 * holdings, we shift the playing board accordingly
2389 * This kludge is needed because in ICS observe mode, we get boards
2390 * of an ongoing game without knowing the variant, and learn about the
2391 * latter only later. This can be because of the move list we requested,
2392 * in which case the game history is refilled from the beginning anyway,
2393 * but also when receiving holdings of a crazyhouse game. In the latter
2394 * case we want to add those holdings to the already received position.
2398 if (appData.debugMode) {
2399 fprintf(debugFP, "Switch board from %s to %s\n",
2400 VariantName(gameInfo.variant), VariantName(newVariant));
2401 setbuf(debugFP, NULL);
2403 shuffleOpenings = 0; /* [HGM] shuffle */
2404 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2408 newWidth = 9; newHeight = 9;
2409 gameInfo.holdingsSize = 7;
2410 case VariantBughouse:
2411 case VariantCrazyhouse:
2412 newHoldingsWidth = 2; break;
2416 newHoldingsWidth = 2;
2417 gameInfo.holdingsSize = 8;
2420 case VariantCapablanca:
2421 case VariantCapaRandom:
2424 newHoldingsWidth = gameInfo.holdingsSize = 0;
2427 if(newWidth != gameInfo.boardWidth ||
2428 newHeight != gameInfo.boardHeight ||
2429 newHoldingsWidth != gameInfo.holdingsWidth ) {
2431 /* shift position to new playing area, if needed */
2432 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2433 for(i=0; i<BOARD_HEIGHT; i++)
2434 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2435 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437 for(i=0; i<newHeight; i++) {
2438 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2439 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2441 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2442 for(i=0; i<BOARD_HEIGHT; i++)
2443 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2444 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447 gameInfo.boardWidth = newWidth;
2448 gameInfo.boardHeight = newHeight;
2449 gameInfo.holdingsWidth = newHoldingsWidth;
2450 gameInfo.variant = newVariant;
2451 InitDrawingSizes(-2, 0);
2452 } else gameInfo.variant = newVariant;
2453 CopyBoard(oldBoard, board); // remember correctly formatted board
2454 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2455 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 static int loggedOn = FALSE;
2460 /*-- Game start info cache: --*/
2462 char gs_kind[MSG_SIZ];
2463 static char player1Name[128] = "";
2464 static char player2Name[128] = "";
2465 static char cont_seq[] = "\n\\ ";
2466 static int player1Rating = -1;
2467 static int player2Rating = -1;
2468 /*----------------------------*/
2470 ColorClass curColor = ColorNormal;
2471 int suppressKibitz = 0;
2474 Boolean soughtPending = FALSE;
2475 Boolean seekGraphUp;
2476 #define MAX_SEEK_ADS 200
2478 char *seekAdList[MAX_SEEK_ADS];
2479 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2480 float tcList[MAX_SEEK_ADS];
2481 char colorList[MAX_SEEK_ADS];
2482 int nrOfSeekAds = 0;
2483 int minRating = 1010, maxRating = 2800;
2484 int hMargin = 10, vMargin = 20, h, w;
2485 extern int squareSize, lineGap;
2490 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2491 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2492 if(r < minRating+100 && r >=0 ) r = minRating+100;
2493 if(r > maxRating) r = maxRating;
2494 if(tc < 1.) tc = 1.;
2495 if(tc > 95.) tc = 95.;
2496 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2497 y = ((double)r - minRating)/(maxRating - minRating)
2498 * (h-vMargin-squareSize/8-1) + vMargin;
2499 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2500 if(strstr(seekAdList[i], " u ")) color = 1;
2501 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2502 !strstr(seekAdList[i], "bullet") &&
2503 !strstr(seekAdList[i], "blitz") &&
2504 !strstr(seekAdList[i], "standard") ) color = 2;
2505 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2506 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2512 char buf[MSG_SIZ], *ext = "";
2513 VariantClass v = StringToVariant(type);
2514 if(strstr(type, "wild")) {
2515 ext = type + 4; // append wild number
2516 if(v == VariantFischeRandom) type = "chess960"; else
2517 if(v == VariantLoadable) type = "setup"; else
2518 type = VariantName(v);
2520 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2521 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2522 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2523 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2524 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2525 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2526 seekNrList[nrOfSeekAds] = nr;
2527 zList[nrOfSeekAds] = 0;
2528 seekAdList[nrOfSeekAds++] = StrSave(buf);
2529 if(plot) PlotSeekAd(nrOfSeekAds-1);
2534 EraseSeekDot (int i)
2536 int x = xList[i], y = yList[i], d=squareSize/4, k;
2537 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2538 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2539 // now replot every dot that overlapped
2540 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2541 int xx = xList[k], yy = yList[k];
2542 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2543 DrawSeekDot(xx, yy, colorList[k]);
2548 RemoveSeekAd (int nr)
2551 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2553 if(seekAdList[i]) free(seekAdList[i]);
2554 seekAdList[i] = seekAdList[--nrOfSeekAds];
2555 seekNrList[i] = seekNrList[nrOfSeekAds];
2556 ratingList[i] = ratingList[nrOfSeekAds];
2557 colorList[i] = colorList[nrOfSeekAds];
2558 tcList[i] = tcList[nrOfSeekAds];
2559 xList[i] = xList[nrOfSeekAds];
2560 yList[i] = yList[nrOfSeekAds];
2561 zList[i] = zList[nrOfSeekAds];
2562 seekAdList[nrOfSeekAds] = NULL;
2568 MatchSoughtLine (char *line)
2570 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2571 int nr, base, inc, u=0; char dummy;
2573 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2574 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2576 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2578 // match: compact and save the line
2579 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2589 if(!seekGraphUp) return FALSE;
2590 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2591 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2593 DrawSeekBackground(0, 0, w, h);
2594 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2595 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2596 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2597 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2599 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602 snprintf(buf, MSG_SIZ, "%d", i);
2603 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2607 for(i=1; i<100; i+=(i<10?1:5)) {
2608 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2609 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2610 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2612 snprintf(buf, MSG_SIZ, "%d", i);
2613 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2621 SeekGraphClick (ClickType click, int x, int y, int moving)
2623 static int lastDown = 0, displayed = 0, lastSecond;
2624 if(y < 0) return FALSE;
2625 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2626 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2627 if(!seekGraphUp) return FALSE;
2628 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2629 DrawPosition(TRUE, NULL);
2632 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2633 if(click == Release || moving) return FALSE;
2635 soughtPending = TRUE;
2636 SendToICS(ics_prefix);
2637 SendToICS("sought\n"); // should this be "sought all"?
2638 } else { // issue challenge based on clicked ad
2639 int dist = 10000; int i, closest = 0, second = 0;
2640 for(i=0; i<nrOfSeekAds; i++) {
2641 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2642 if(d < dist) { dist = d; closest = i; }
2643 second += (d - zList[i] < 120); // count in-range ads
2644 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648 second = (second > 1);
2649 if(displayed != closest || second != lastSecond) {
2650 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2651 lastSecond = second; displayed = closest;
2653 if(click == Press) {
2654 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657 } // on press 'hit', only show info
2658 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2659 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2660 SendToICS(ics_prefix);
2662 return TRUE; // let incoming board of started game pop down the graph
2663 } else if(click == Release) { // release 'miss' is ignored
2664 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2665 if(moving == 2) { // right up-click
2666 nrOfSeekAds = 0; // refresh graph
2667 soughtPending = TRUE;
2668 SendToICS(ics_prefix);
2669 SendToICS("sought\n"); // should this be "sought all"?
2672 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2673 // press miss or release hit 'pop down' seek graph
2674 seekGraphUp = FALSE;
2675 DrawPosition(TRUE, NULL);
2681 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2683 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2684 #define STARTED_NONE 0
2685 #define STARTED_MOVES 1
2686 #define STARTED_BOARD 2
2687 #define STARTED_OBSERVE 3
2688 #define STARTED_HOLDINGS 4
2689 #define STARTED_CHATTER 5
2690 #define STARTED_COMMENT 6
2691 #define STARTED_MOVES_NOHIDE 7
2693 static int started = STARTED_NONE;
2694 static char parse[20000];
2695 static int parse_pos = 0;
2696 static char buf[BUF_SIZE + 1];
2697 static int firstTime = TRUE, intfSet = FALSE;
2698 static ColorClass prevColor = ColorNormal;
2699 static int savingComment = FALSE;
2700 static int cmatch = 0; // continuation sequence match
2707 int backup; /* [DM] For zippy color lines */
2709 char talker[MSG_SIZ]; // [HGM] chat
2712 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2714 if (appData.debugMode) {
2716 fprintf(debugFP, "<ICS: ");
2717 show_bytes(debugFP, data, count);
2718 fprintf(debugFP, "\n");
2722 if (appData.debugMode) { int f = forwardMostMove;
2723 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2724 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2725 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728 /* If last read ended with a partial line that we couldn't parse,
2729 prepend it to the new read and try again. */
2730 if (leftover_len > 0) {
2731 for (i=0; i<leftover_len; i++)
2732 buf[i] = buf[leftover_start + i];
2735 /* copy new characters into the buffer */
2736 bp = buf + leftover_len;
2737 buf_len=leftover_len;
2738 for (i=0; i<count; i++)
2741 if (data[i] == '\r')
2744 // join lines split by ICS?
2745 if (!appData.noJoin)
2748 Joining just consists of finding matches against the
2749 continuation sequence, and discarding that sequence
2750 if found instead of copying it. So, until a match
2751 fails, there's nothing to do since it might be the
2752 complete sequence, and thus, something we don't want
2755 if (data[i] == cont_seq[cmatch])
2758 if (cmatch == strlen(cont_seq))
2760 cmatch = 0; // complete match. just reset the counter
2763 it's possible for the ICS to not include the space
2764 at the end of the last word, making our [correct]
2765 join operation fuse two separate words. the server
2766 does this when the space occurs at the width setting.
2768 if (!buf_len || buf[buf_len-1] != ' ')
2779 match failed, so we have to copy what matched before
2780 falling through and copying this character. In reality,
2781 this will only ever be just the newline character, but
2782 it doesn't hurt to be precise.
2784 strncpy(bp, cont_seq, cmatch);
2796 buf[buf_len] = NULLCHAR;
2797 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2802 while (i < buf_len) {
2803 /* Deal with part of the TELNET option negotiation
2804 protocol. We refuse to do anything beyond the
2805 defaults, except that we allow the WILL ECHO option,
2806 which ICS uses to turn off password echoing when we are
2807 directly connected to it. We reject this option
2808 if localLineEditing mode is on (always on in xboard)
2809 and we are talking to port 23, which might be a real
2810 telnet server that will try to keep WILL ECHO on permanently.
2812 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2813 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2814 unsigned char option;
2816 switch ((unsigned char) buf[++i]) {
2818 if (appData.debugMode)
2819 fprintf(debugFP, "\n<WILL ");
2820 switch (option = (unsigned char) buf[++i]) {
2822 if (appData.debugMode)
2823 fprintf(debugFP, "ECHO ");
2824 /* Reply only if this is a change, according
2825 to the protocol rules. */
2826 if (remoteEchoOption) break;
2827 if (appData.localLineEditing &&
2828 atoi(appData.icsPort) == TN_PORT) {
2829 TelnetRequest(TN_DONT, TN_ECHO);
2832 TelnetRequest(TN_DO, TN_ECHO);
2833 remoteEchoOption = TRUE;
2837 if (appData.debugMode)
2838 fprintf(debugFP, "%d ", option);
2839 /* Whatever this is, we don't want it. */
2840 TelnetRequest(TN_DONT, option);
2845 if (appData.debugMode)
2846 fprintf(debugFP, "\n<WONT ");
2847 switch (option = (unsigned char) buf[++i]) {
2849 if (appData.debugMode)
2850 fprintf(debugFP, "ECHO ");
2851 /* Reply only if this is a change, according
2852 to the protocol rules. */
2853 if (!remoteEchoOption) break;
2855 TelnetRequest(TN_DONT, TN_ECHO);
2856 remoteEchoOption = FALSE;
2859 if (appData.debugMode)
2860 fprintf(debugFP, "%d ", (unsigned char) option);
2861 /* Whatever this is, it must already be turned
2862 off, because we never agree to turn on
2863 anything non-default, so according to the
2864 protocol rules, we don't reply. */
2869 if (appData.debugMode)
2870 fprintf(debugFP, "\n<DO ");
2871 switch (option = (unsigned char) buf[++i]) {
2873 /* Whatever this is, we refuse to do it. */
2874 if (appData.debugMode)
2875 fprintf(debugFP, "%d ", option);
2876 TelnetRequest(TN_WONT, option);
2881 if (appData.debugMode)
2882 fprintf(debugFP, "\n<DONT ");
2883 switch (option = (unsigned char) buf[++i]) {
2885 if (appData.debugMode)
2886 fprintf(debugFP, "%d ", option);
2887 /* Whatever this is, we are already not doing
2888 it, because we never agree to do anything
2889 non-default, so according to the protocol
2890 rules, we don't reply. */
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<IAC ");
2897 /* Doubled IAC; pass it through */
2901 if (appData.debugMode)
2902 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2903 /* Drop all other telnet commands on the floor */
2906 if (oldi > next_out)
2907 SendToPlayer(&buf[next_out], oldi - next_out);
2913 /* OK, this at least will *usually* work */
2914 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918 if (loggedOn && !intfSet) {
2919 if (ics_type == ICS_ICC) {
2920 snprintf(str, MSG_SIZ,
2921 "/set-quietly interface %s\n/set-quietly style 12\n",
2923 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2924 strcat(str, "/set-2 51 1\n/set seek 1\n");
2925 } else if (ics_type == ICS_CHESSNET) {
2926 snprintf(str, MSG_SIZ, "/style 12\n");
2928 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2929 strcat(str, programVersion);
2930 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2931 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2932 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2934 strcat(str, "$iset nohighlight 1\n");
2936 strcat(str, "$iset lock 1\n$style 12\n");
2939 NotifyFrontendLogin();
2943 if (started == STARTED_COMMENT) {
2944 /* Accumulate characters in comment */
2945 parse[parse_pos++] = buf[i];
2946 if (buf[i] == '\n') {
2947 parse[parse_pos] = NULLCHAR;
2948 if(chattingPartner>=0) {
2950 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2951 OutputChatMessage(chattingPartner, mess);
2952 chattingPartner = -1;
2953 next_out = i+1; // [HGM] suppress printing in ICS window
2955 if(!suppressKibitz) // [HGM] kibitz
2956 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2957 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2958 int nrDigit = 0, nrAlph = 0, j;
2959 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2960 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2961 parse[parse_pos] = NULLCHAR;
2962 // try to be smart: if it does not look like search info, it should go to
2963 // ICS interaction window after all, not to engine-output window.
2964 for(j=0; j<parse_pos; j++) { // count letters and digits
2965 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2966 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2967 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2969 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2970 int depth=0; float score;
2971 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2972 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2973 pvInfoList[forwardMostMove-1].depth = depth;
2974 pvInfoList[forwardMostMove-1].score = 100*score;
2976 OutputKibitz(suppressKibitz, parse);
2979 if(gameMode == IcsObserving) // restore original ICS messages
2980 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2982 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2983 SendToPlayer(tmp, strlen(tmp));
2985 next_out = i+1; // [HGM] suppress printing in ICS window
2987 started = STARTED_NONE;
2989 /* Don't match patterns against characters in comment */
2994 if (started == STARTED_CHATTER) {
2995 if (buf[i] != '\n') {
2996 /* Don't match patterns against characters in chatter */
3000 started = STARTED_NONE;
3001 if(suppressKibitz) next_out = i+1;
3004 /* Kludge to deal with rcmd protocol */
3005 if (firstTime && looking_at(buf, &i, "\001*")) {
3006 DisplayFatalError(&buf[1], 0, 1);
3012 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015 if (appData.debugMode)
3016 fprintf(debugFP, "ics_type %d\n", ics_type);
3019 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3020 ics_type = ICS_FICS;
3022 if (appData.debugMode)
3023 fprintf(debugFP, "ics_type %d\n", ics_type);
3026 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3027 ics_type = ICS_CHESSNET;
3029 if (appData.debugMode)
3030 fprintf(debugFP, "ics_type %d\n", ics_type);
3035 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3036 looking_at(buf, &i, "Logging you in as \"*\"") ||
3037 looking_at(buf, &i, "will be \"*\""))) {
3038 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3044 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3045 DisplayIcsInteractionTitle(buf);
3046 have_set_title = TRUE;
3049 /* skip finger notes */
3050 if (started == STARTED_NONE &&
3051 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3052 (buf[i] == '1' && buf[i+1] == '0')) &&
3053 buf[i+2] == ':' && buf[i+3] == ' ') {
3054 started = STARTED_CHATTER;
3060 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3061 if(appData.seekGraph) {
3062 if(soughtPending && MatchSoughtLine(buf+i)) {
3063 i = strstr(buf+i, "rated") - buf;
3064 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065 next_out = leftover_start = i;
3066 started = STARTED_CHATTER;
3067 suppressKibitz = TRUE;
3070 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3071 && looking_at(buf, &i, "* ads displayed")) {
3072 soughtPending = FALSE;
3077 if(appData.autoRefresh) {
3078 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3079 int s = (ics_type == ICS_ICC); // ICC format differs
3081 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3082 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3083 looking_at(buf, &i, "*% "); // eat prompt
3084 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3085 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086 next_out = i; // suppress
3089 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3090 char *p = star_match[0];
3092 if(seekGraphUp) RemoveSeekAd(atoi(p));
3093 while(*p && *p++ != ' '); // next
3095 looking_at(buf, &i, "*% "); // eat prompt
3096 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3103 /* skip formula vars */
3104 if (started == STARTED_NONE &&
3105 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3106 started = STARTED_CHATTER;
3111 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3112 if (appData.autoKibitz && started == STARTED_NONE &&
3113 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3114 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3115 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3116 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3117 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3118 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3119 suppressKibitz = TRUE;
3120 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3123 && (gameMode == IcsPlayingWhite)) ||
3124 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3125 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3126 started = STARTED_CHATTER; // own kibitz we simply discard
3128 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3129 parse_pos = 0; parse[0] = NULLCHAR;
3130 savingComment = TRUE;
3131 suppressKibitz = gameMode != IcsObserving ? 2 :
3132 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3137 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3138 && atoi(star_match[0])) {
3139 // suppress the acknowledgements of our own autoKibitz
3141 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3142 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3143 SendToPlayer(star_match[0], strlen(star_match[0]));
3144 if(looking_at(buf, &i, "*% ")) // eat prompt
3145 suppressKibitz = FALSE;
3149 } // [HGM] kibitz: end of patch
3151 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3153 // [HGM] chat: intercept tells by users for which we have an open chat window
3155 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3156 looking_at(buf, &i, "* whispers:") ||
3157 looking_at(buf, &i, "* kibitzes:") ||
3158 looking_at(buf, &i, "* shouts:") ||
3159 looking_at(buf, &i, "* c-shouts:") ||
3160 looking_at(buf, &i, "--> * ") ||
3161 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3162 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3163 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3164 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3166 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3167 chattingPartner = -1;
3169 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3170 for(p=0; p<MAX_CHAT; p++) {
3171 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3172 talker[0] = '['; strcat(talker, "] ");
3173 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3174 chattingPartner = p; break;
3177 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3178 for(p=0; p<MAX_CHAT; p++) {
3179 if(!strcmp("kibitzes", chatPartner[p])) {
3180 talker[0] = '['; strcat(talker, "] ");
3181 chattingPartner = p; break;
3184 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3185 for(p=0; p<MAX_CHAT; p++) {
3186 if(!strcmp("whispers", chatPartner[p])) {
3187 talker[0] = '['; strcat(talker, "] ");
3188 chattingPartner = p; break;
3191 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3192 if(buf[i-8] == '-' && buf[i-3] == 't')
3193 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3194 if(!strcmp("c-shouts", chatPartner[p])) {
3195 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3196 chattingPartner = p; break;
3199 if(chattingPartner < 0)
3200 for(p=0; p<MAX_CHAT; p++) {
3201 if(!strcmp("shouts", chatPartner[p])) {
3202 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3203 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3204 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3205 chattingPartner = p; break;
3209 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3210 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3211 talker[0] = 0; Colorize(ColorTell, FALSE);
3212 chattingPartner = p; break;
3214 if(chattingPartner<0) i = oldi; else {
3215 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3216 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3217 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3218 started = STARTED_COMMENT;
3219 parse_pos = 0; parse[0] = NULLCHAR;
3220 savingComment = 3 + chattingPartner; // counts as TRUE
3221 suppressKibitz = TRUE;
3224 } // [HGM] chat: end of patch
3227 if (appData.zippyTalk || appData.zippyPlay) {
3228 /* [DM] Backup address for color zippy lines */
3230 if (loggedOn == TRUE)
3231 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3232 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3234 } // [DM] 'else { ' deleted
3236 /* Regular tells and says */
3237 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3238 looking_at(buf, &i, "* (your partner) tells you: ") ||
3239 looking_at(buf, &i, "* says: ") ||
3240 /* Don't color "message" or "messages" output */
3241 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3242 looking_at(buf, &i, "*. * at *:*: ") ||
3243 looking_at(buf, &i, "--* (*:*): ") ||
3244 /* Message notifications (same color as tells) */
3245 looking_at(buf, &i, "* has left a message ") ||
3246 looking_at(buf, &i, "* just sent you a message:\n") ||
3247 /* Whispers and kibitzes */
3248 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3249 looking_at(buf, &i, "* kibitzes: ") ||
3251 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3253 if (tkind == 1 && strchr(star_match[0], ':')) {
3254 /* Avoid "tells you:" spoofs in channels */
3257 if (star_match[0][0] == NULLCHAR ||
3258 strchr(star_match[0], ' ') ||
3259 (tkind == 3 && strchr(star_match[1], ' '))) {
3260 /* Reject bogus matches */
3263 if (appData.colorize) {
3264 if (oldi > next_out) {
3265 SendToPlayer(&buf[next_out], oldi - next_out);
3270 Colorize(ColorTell, FALSE);
3271 curColor = ColorTell;
3274 Colorize(ColorKibitz, FALSE);
3275 curColor = ColorKibitz;
3278 p = strrchr(star_match[1], '(');
3285 Colorize(ColorChannel1, FALSE);
3286 curColor = ColorChannel1;
3288 Colorize(ColorChannel, FALSE);
3289 curColor = ColorChannel;
3293 curColor = ColorNormal;
3297 if (started == STARTED_NONE && appData.autoComment &&
3298 (gameMode == IcsObserving ||
3299 gameMode == IcsPlayingWhite ||
3300 gameMode == IcsPlayingBlack)) {
3301 parse_pos = i - oldi;
3302 memcpy(parse, &buf[oldi], parse_pos);
3303 parse[parse_pos] = NULLCHAR;
3304 started = STARTED_COMMENT;
3305 savingComment = TRUE;
3307 started = STARTED_CHATTER;
3308 savingComment = FALSE;
3315 if (looking_at(buf, &i, "* s-shouts: ") ||
3316 looking_at(buf, &i, "* c-shouts: ")) {
3317 if (appData.colorize) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3322 Colorize(ColorSShout, FALSE);
3323 curColor = ColorSShout;
3326 started = STARTED_CHATTER;
3330 if (looking_at(buf, &i, "--->")) {
3335 if (looking_at(buf, &i, "* shouts: ") ||
3336 looking_at(buf, &i, "--> ")) {
3337 if (appData.colorize) {
3338 if (oldi > next_out) {
3339 SendToPlayer(&buf[next_out], oldi - next_out);
3342 Colorize(ColorShout, FALSE);
3343 curColor = ColorShout;
3346 started = STARTED_CHATTER;
3350 if (looking_at( buf, &i, "Challenge:")) {
3351 if (appData.colorize) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3356 Colorize(ColorChallenge, FALSE);
3357 curColor = ColorChallenge;
3363 if (looking_at(buf, &i, "* offers you") ||
3364 looking_at(buf, &i, "* offers to be") ||
3365 looking_at(buf, &i, "* would like to") ||
3366 looking_at(buf, &i, "* requests to") ||
3367 looking_at(buf, &i, "Your opponent offers") ||
3368 looking_at(buf, &i, "Your opponent requests")) {
3370 if (appData.colorize) {
3371 if (oldi > next_out) {
3372 SendToPlayer(&buf[next_out], oldi - next_out);
3375 Colorize(ColorRequest, FALSE);
3376 curColor = ColorRequest;
3381 if (looking_at(buf, &i, "* (*) seeking")) {
3382 if (appData.colorize) {
3383 if (oldi > next_out) {
3384 SendToPlayer(&buf[next_out], oldi - next_out);
3387 Colorize(ColorSeek, FALSE);
3388 curColor = ColorSeek;
3393 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3395 if (looking_at(buf, &i, "\\ ")) {
3396 if (prevColor != ColorNormal) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 Colorize(prevColor, TRUE);
3402 curColor = prevColor;
3404 if (savingComment) {
3405 parse_pos = i - oldi;
3406 memcpy(parse, &buf[oldi], parse_pos);
3407 parse[parse_pos] = NULLCHAR;
3408 started = STARTED_COMMENT;
3409 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3410 chattingPartner = savingComment - 3; // kludge to remember the box
3412 started = STARTED_CHATTER;
3417 if (looking_at(buf, &i, "Black Strength :") ||
3418 looking_at(buf, &i, "<<< style 10 board >>>") ||
3419 looking_at(buf, &i, "<10>") ||
3420 looking_at(buf, &i, "#@#")) {
3421 /* Wrong board style */
3423 SendToICS(ics_prefix);
3424 SendToICS("set style 12\n");
3425 SendToICS(ics_prefix);
3426 SendToICS("refresh\n");
3430 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3432 have_sent_ICS_logon = 1;
3436 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3437 (looking_at(buf, &i, "\n<12> ") ||
3438 looking_at(buf, &i, "<12> "))) {
3440 if (oldi > next_out) {
3441 SendToPlayer(&buf[next_out], oldi - next_out);
3444 started = STARTED_BOARD;
3449 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3450 looking_at(buf, &i, "<b1> ")) {
3451 if (oldi > next_out) {
3452 SendToPlayer(&buf[next_out], oldi - next_out);
3455 started = STARTED_HOLDINGS;
3460 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3462 /* Header for a move list -- first line */
3464 switch (ics_getting_history) {
3468 case BeginningOfGame:
3469 /* User typed "moves" or "oldmoves" while we
3470 were idle. Pretend we asked for these
3471 moves and soak them up so user can step
3472 through them and/or save them.
3475 gameMode = IcsObserving;
3478 ics_getting_history = H_GOT_UNREQ_HEADER;
3480 case EditGame: /*?*/
3481 case EditPosition: /*?*/
3482 /* Should above feature work in these modes too? */
3483 /* For now it doesn't */
3484 ics_getting_history = H_GOT_UNWANTED_HEADER;
3487 ics_getting_history = H_GOT_UNWANTED_HEADER;
3492 /* Is this the right one? */
3493 if (gameInfo.white && gameInfo.black &&
3494 strcmp(gameInfo.white, star_match[0]) == 0 &&
3495 strcmp(gameInfo.black, star_match[2]) == 0) {
3497 ics_getting_history = H_GOT_REQ_HEADER;
3500 case H_GOT_REQ_HEADER:
3501 case H_GOT_UNREQ_HEADER:
3502 case H_GOT_UNWANTED_HEADER:
3503 case H_GETTING_MOVES:
3504 /* Should not happen */
3505 DisplayError(_("Error gathering move list: two headers"), 0);
3506 ics_getting_history = H_FALSE;
3510 /* Save player ratings into gameInfo if needed */
3511 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3512 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3513 (gameInfo.whiteRating == -1 ||
3514 gameInfo.blackRating == -1)) {
3516 gameInfo.whiteRating = string_to_rating(star_match[1]);
3517 gameInfo.blackRating = string_to_rating(star_match[3]);
3518 if (appData.debugMode)
3519 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3520 gameInfo.whiteRating, gameInfo.blackRating);
3525 if (looking_at(buf, &i,
3526 "* * match, initial time: * minute*, increment: * second")) {
3527 /* Header for a move list -- second line */
3528 /* Initial board will follow if this is a wild game */
3529 if (gameInfo.event != NULL) free(gameInfo.event);
3530 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3531 gameInfo.event = StrSave(str);
3532 /* [HGM] we switched variant. Translate boards if needed. */
3533 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537 if (looking_at(buf, &i, "Move ")) {
3538 /* Beginning of a move list */
3539 switch (ics_getting_history) {
3541 /* Normally should not happen */
3542 /* Maybe user hit reset while we were parsing */
3545 /* Happens if we are ignoring a move list that is not
3546 * the one we just requested. Common if the user
3547 * tries to observe two games without turning off
3550 case H_GETTING_MOVES:
3551 /* Should not happen */
3552 DisplayError(_("Error gathering move list: nested"), 0);
3553 ics_getting_history = H_FALSE;
3555 case H_GOT_REQ_HEADER:
3556 ics_getting_history = H_GETTING_MOVES;
3557 started = STARTED_MOVES;
3559 if (oldi > next_out) {
3560 SendToPlayer(&buf[next_out], oldi - next_out);
3563 case H_GOT_UNREQ_HEADER:
3564 ics_getting_history = H_GETTING_MOVES;
3565 started = STARTED_MOVES_NOHIDE;
3568 case H_GOT_UNWANTED_HEADER:
3569 ics_getting_history = H_FALSE;
3575 if (looking_at(buf, &i, "% ") ||
3576 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3577 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3578 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3579 soughtPending = FALSE;
3583 if(suppressKibitz) next_out = i;
3584 savingComment = FALSE;
3588 case STARTED_MOVES_NOHIDE:
3589 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3590 parse[parse_pos + i - oldi] = NULLCHAR;
3591 ParseGameHistory(parse);
3593 if (appData.zippyPlay && first.initDone) {
3594 FeedMovesToProgram(&first, forwardMostMove);
3595 if (gameMode == IcsPlayingWhite) {
3596 if (WhiteOnMove(forwardMostMove)) {
3597 if (first.sendTime) {
3598 if (first.useColors) {
3599 SendToProgram("black\n", &first);
3601 SendTimeRemaining(&first, TRUE);
3603 if (first.useColors) {
3604 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3606 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3607 first.maybeThinking = TRUE;
3609 if (first.usePlayother) {
3610 if (first.sendTime) {
3611 SendTimeRemaining(&first, TRUE);
3613 SendToProgram("playother\n", &first);
3619 } else if (gameMode == IcsPlayingBlack) {
3620 if (!WhiteOnMove(forwardMostMove)) {
3621 if (first.sendTime) {
3622 if (first.useColors) {
3623 SendToProgram("white\n", &first);
3625 SendTimeRemaining(&first, FALSE);
3627 if (first.useColors) {
3628 SendToProgram("black\n", &first);
3630 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3631 first.maybeThinking = TRUE;
3633 if (first.usePlayother) {
3634 if (first.sendTime) {
3635 SendTimeRemaining(&first, FALSE);
3637 SendToProgram("playother\n", &first);
3646 if (gameMode == IcsObserving && ics_gamenum == -1) {
3647 /* Moves came from oldmoves or moves command
3648 while we weren't doing anything else.
3650 currentMove = forwardMostMove;
3651 ClearHighlights();/*!!could figure this out*/
3652 flipView = appData.flipView;
3653 DrawPosition(TRUE, boards[currentMove]);
3654 DisplayBothClocks();
3655 snprintf(str, MSG_SIZ, "%s %s %s",
3656 gameInfo.white, _("vs."), gameInfo.black);
3660 /* Moves were history of an active game */
3661 if (gameInfo.resultDetails != NULL) {
3662 free(gameInfo.resultDetails);
3663 gameInfo.resultDetails = NULL;
3666 HistorySet(parseList, backwardMostMove,
3667 forwardMostMove, currentMove-1);
3668 DisplayMove(currentMove - 1);
3669 if (started == STARTED_MOVES) next_out = i;
3670 started = STARTED_NONE;
3671 ics_getting_history = H_FALSE;
3674 case STARTED_OBSERVE:
3675 started = STARTED_NONE;
3676 SendToICS(ics_prefix);
3677 SendToICS("refresh\n");
3683 if(bookHit) { // [HGM] book: simulate book reply
3684 static char bookMove[MSG_SIZ]; // a bit generous?
3686 programStats.nodes = programStats.depth = programStats.time =
3687 programStats.score = programStats.got_only_move = 0;
3688 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3690 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3691 strcat(bookMove, bookHit);
3692 HandleMachineMove(bookMove, &first);
3697 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3698 started == STARTED_HOLDINGS ||
3699 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3700 /* Accumulate characters in move list or board */
3701 parse[parse_pos++] = buf[i];
3704 /* Start of game messages. Mostly we detect start of game
3705 when the first board image arrives. On some versions
3706 of the ICS, though, we need to do a "refresh" after starting
3707 to observe in order to get the current board right away. */
3708 if (looking_at(buf, &i, "Adding game * to observation list")) {
3709 started = STARTED_OBSERVE;
3713 /* Handle auto-observe */
3714 if (appData.autoObserve &&
3715 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3716 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3718 /* Choose the player that was highlighted, if any. */
3719 if (star_match[0][0] == '\033' ||
3720 star_match[1][0] != '\033') {
3721 player = star_match[0];
3723 player = star_match[2];
3725 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3726 ics_prefix, StripHighlightAndTitle(player));
3729 /* Save ratings from notify string */
3730 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3731 player1Rating = string_to_rating(star_match[1]);
3732 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3733 player2Rating = string_to_rating(star_match[3]);
3735 if (appData.debugMode)
3737 "Ratings from 'Game notification:' %s %d, %s %d\n",
3738 player1Name, player1Rating,
3739 player2Name, player2Rating);
3744 /* Deal with automatic examine mode after a game,
3745 and with IcsObserving -> IcsExamining transition */
3746 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3747 looking_at(buf, &i, "has made you an examiner of game *")) {
3749 int gamenum = atoi(star_match[0]);
3750 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3751 gamenum == ics_gamenum) {
3752 /* We were already playing or observing this game;
3753 no need to refetch history */
3754 gameMode = IcsExamining;
3756 pauseExamForwardMostMove = forwardMostMove;
3757 } else if (currentMove < forwardMostMove) {
3758 ForwardInner(forwardMostMove);
3761 /* I don't think this case really can happen */
3762 SendToICS(ics_prefix);
3763 SendToICS("refresh\n");
3768 /* Error messages */
3769 // if (ics_user_moved) {
3770 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3771 if (looking_at(buf, &i, "Illegal move") ||
3772 looking_at(buf, &i, "Not a legal move") ||
3773 looking_at(buf, &i, "Your king is in check") ||
3774 looking_at(buf, &i, "It isn't your turn") ||
3775 looking_at(buf, &i, "It is not your move")) {
3777 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3778 currentMove = forwardMostMove-1;
3779 DisplayMove(currentMove - 1); /* before DMError */
3780 DrawPosition(FALSE, boards[currentMove]);
3781 SwitchClocks(forwardMostMove-1); // [HGM] race
3782 DisplayBothClocks();
3784 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3790 if (looking_at(buf, &i, "still have time") ||
3791 looking_at(buf, &i, "not out of time") ||
3792 looking_at(buf, &i, "either player is out of time") ||
3793 looking_at(buf, &i, "has timeseal; checking")) {
3794 /* We must have called his flag a little too soon */
3795 whiteFlag = blackFlag = FALSE;
3799 if (looking_at(buf, &i, "added * seconds to") ||
3800 looking_at(buf, &i, "seconds were added to")) {
3801 /* Update the clocks */
3802 SendToICS(ics_prefix);
3803 SendToICS("refresh\n");
3807 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3808 ics_clock_paused = TRUE;
3813 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3814 ics_clock_paused = FALSE;
3819 /* Grab player ratings from the Creating: message.
3820 Note we have to check for the special case when
3821 the ICS inserts things like [white] or [black]. */
3822 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3823 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3825 0 player 1 name (not necessarily white)
3827 2 empty, white, or black (IGNORED)
3828 3 player 2 name (not necessarily black)
3831 The names/ratings are sorted out when the game
3832 actually starts (below).
3834 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3835 player1Rating = string_to_rating(star_match[1]);
3836 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3837 player2Rating = string_to_rating(star_match[4]);
3839 if (appData.debugMode)
3841 "Ratings from 'Creating:' %s %d, %s %d\n",
3842 player1Name, player1Rating,
3843 player2Name, player2Rating);
3848 /* Improved generic start/end-of-game messages */
3849 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3850 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3851 /* If tkind == 0: */
3852 /* star_match[0] is the game number */
3853 /* [1] is the white player's name */
3854 /* [2] is the black player's name */
3855 /* For end-of-game: */
3856 /* [3] is the reason for the game end */
3857 /* [4] is a PGN end game-token, preceded by " " */
3858 /* For start-of-game: */
3859 /* [3] begins with "Creating" or "Continuing" */
3860 /* [4] is " *" or empty (don't care). */
3861 int gamenum = atoi(star_match[0]);
3862 char *whitename, *blackname, *why, *endtoken;
3863 ChessMove endtype = EndOfFile;
3866 whitename = star_match[1];
3867 blackname = star_match[2];
3868 why = star_match[3];
3869 endtoken = star_match[4];
3871 whitename = star_match[1];
3872 blackname = star_match[3];
3873 why = star_match[5];
3874 endtoken = star_match[6];
3877 /* Game start messages */
3878 if (strncmp(why, "Creating ", 9) == 0 ||
3879 strncmp(why, "Continuing ", 11) == 0) {
3880 gs_gamenum = gamenum;
3881 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3882 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3883 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3885 if (appData.zippyPlay) {
3886 ZippyGameStart(whitename, blackname);
3889 partnerBoardValid = FALSE; // [HGM] bughouse
3893 /* Game end messages */
3894 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3895 ics_gamenum != gamenum) {
3898 while (endtoken[0] == ' ') endtoken++;
3899 switch (endtoken[0]) {
3902 endtype = GameUnfinished;
3905 endtype = BlackWins;
3908 if (endtoken[1] == '/')
3909 endtype = GameIsDrawn;
3911 endtype = WhiteWins;
3914 GameEnds(endtype, why, GE_ICS);
3916 if (appData.zippyPlay && first.initDone) {
3917 ZippyGameEnd(endtype, why);
3918 if (first.pr == NoProc) {
3919 /* Start the next process early so that we'll
3920 be ready for the next challenge */
3921 StartChessProgram(&first);
3923 /* Send "new" early, in case this command takes
3924 a long time to finish, so that we'll be ready
3925 for the next challenge. */
3926 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934 if (looking_at(buf, &i, "Removing game * from observation") ||
3935 looking_at(buf, &i, "no longer observing game *") ||
3936 looking_at(buf, &i, "Game * (*) has no examiners")) {
3937 if (gameMode == IcsObserving &&
3938 atoi(star_match[0]) == ics_gamenum)
3940 /* icsEngineAnalyze */
3941 if (appData.icsEngineAnalyze) {
3948 ics_user_moved = FALSE;
3953 if (looking_at(buf, &i, "no longer examining game *")) {
3954 if (gameMode == IcsExamining &&
3955 atoi(star_match[0]) == ics_gamenum)
3959 ics_user_moved = FALSE;
3964 /* Advance leftover_start past any newlines we find,
3965 so only partial lines can get reparsed */
3966 if (looking_at(buf, &i, "\n")) {
3967 prevColor = curColor;
3968 if (curColor != ColorNormal) {
3969 if (oldi > next_out) {
3970 SendToPlayer(&buf[next_out], oldi - next_out);
3973 Colorize(ColorNormal, FALSE);
3974 curColor = ColorNormal;
3976 if (started == STARTED_BOARD) {
3977 started = STARTED_NONE;
3978 parse[parse_pos] = NULLCHAR;
3979 ParseBoard12(parse);
3982 /* Send premove here */
3983 if (appData.premove) {
3985 if (currentMove == 0 &&
3986 gameMode == IcsPlayingWhite &&
3987 appData.premoveWhite) {
3988 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3989 if (appData.debugMode)
3990 fprintf(debugFP, "Sending premove:\n");
3992 } else if (currentMove == 1 &&
3993 gameMode == IcsPlayingBlack &&
3994 appData.premoveBlack) {
3995 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3996 if (appData.debugMode)
3997 fprintf(debugFP, "Sending premove:\n");
3999 } else if (gotPremove) {
4001 ClearPremoveHighlights();
4002 if (appData.debugMode)
4003 fprintf(debugFP, "Sending premove:\n");
4004 UserMoveEvent(premoveFromX, premoveFromY,
4005 premoveToX, premoveToY,
4010 /* Usually suppress following prompt */
4011 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4012 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4013 if (looking_at(buf, &i, "*% ")) {
4014 savingComment = FALSE;
4019 } else if (started == STARTED_HOLDINGS) {
4021 char new_piece[MSG_SIZ];
4022 started = STARTED_NONE;
4023 parse[parse_pos] = NULLCHAR;
4024 if (appData.debugMode)
4025 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4026 parse, currentMove);
4027 if (sscanf(parse, " game %d", &gamenum) == 1) {
4028 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4029 if (gameInfo.variant == VariantNormal) {
4030 /* [HGM] We seem to switch variant during a game!
4031 * Presumably no holdings were displayed, so we have
4032 * to move the position two files to the right to
4033 * create room for them!
4035 VariantClass newVariant;
4036 switch(gameInfo.boardWidth) { // base guess on board width
4037 case 9: newVariant = VariantShogi; break;
4038 case 10: newVariant = VariantGreat; break;
4039 default: newVariant = VariantCrazyhouse; break;
4041 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4042 /* Get a move list just to see the header, which
4043 will tell us whether this is really bug or zh */
4044 if (ics_getting_history == H_FALSE) {
4045 ics_getting_history = H_REQUESTED;
4046 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050 new_piece[0] = NULLCHAR;
4051 sscanf(parse, "game %d white [%s black [%s <- %s",
4052 &gamenum, white_holding, black_holding,
4054 white_holding[strlen(white_holding)-1] = NULLCHAR;
4055 black_holding[strlen(black_holding)-1] = NULLCHAR;
4056 /* [HGM] copy holdings to board holdings area */
4057 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4058 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4059 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4061 if (appData.zippyPlay && first.initDone) {
4062 ZippyHoldings(white_holding, black_holding,
4066 if (tinyLayout || smallLayout) {
4067 char wh[16], bh[16];
4068 PackHolding(wh, white_holding);
4069 PackHolding(bh, black_holding);
4070 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4071 gameInfo.white, gameInfo.black);
4073 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4074 gameInfo.white, white_holding, _("vs."),
4075 gameInfo.black, black_holding);
4077 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4078 DrawPosition(FALSE, boards[currentMove]);
4080 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4081 sscanf(parse, "game %d white [%s black [%s <- %s",
4082 &gamenum, white_holding, black_holding,
4084 white_holding[strlen(white_holding)-1] = NULLCHAR;
4085 black_holding[strlen(black_holding)-1] = NULLCHAR;
4086 /* [HGM] copy holdings to partner-board holdings area */
4087 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4088 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4089 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4090 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4091 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094 /* Suppress following prompt */
4095 if (looking_at(buf, &i, "*% ")) {
4096 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4097 savingComment = FALSE;
4105 i++; /* skip unparsed character and loop back */
4108 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4109 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4110 // SendToPlayer(&buf[next_out], i - next_out);
4111 started != STARTED_HOLDINGS && leftover_start > next_out) {
4112 SendToPlayer(&buf[next_out], leftover_start - next_out);
4116 leftover_len = buf_len - leftover_start;
4117 /* if buffer ends with something we couldn't parse,
4118 reparse it after appending the next read */
4120 } else if (count == 0) {
4121 RemoveInputSource(isr);
4122 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4124 DisplayFatalError(_("Error reading from ICS"), error, 1);
4129 /* Board style 12 looks like this:
4131 <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
4133 * The "<12> " is stripped before it gets to this routine. The two
4134 * trailing 0's (flip state and clock ticking) are later addition, and
4135 * some chess servers may not have them, or may have only the first.
4136 * Additional trailing fields may be added in the future.
4139 #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"
4141 #define RELATION_OBSERVING_PLAYED 0
4142 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4143 #define RELATION_PLAYING_MYMOVE 1
4144 #define RELATION_PLAYING_NOTMYMOVE -1
4145 #define RELATION_EXAMINING 2
4146 #define RELATION_ISOLATED_BOARD -3
4147 #define RELATION_STARTING_POSITION -4 /* FICS only */
4150 ParseBoard12 (char *string)
4152 GameMode newGameMode;
4153 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4154 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4155 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4156 char to_play, board_chars[200];
4157 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4158 char black[32], white[32];
4160 int prevMove = currentMove;
4163 int fromX, fromY, toX, toY;
4165 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4166 char *bookHit = NULL; // [HGM] book
4167 Boolean weird = FALSE, reqFlag = FALSE;
4169 fromX = fromY = toX = toY = -1;
4173 if (appData.debugMode)
4174 fprintf(debugFP, _("Parsing board: %s\n"), string);
4176 move_str[0] = NULLCHAR;
4177 elapsed_time[0] = NULLCHAR;
4178 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4180 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4181 if(string[i] == ' ') { ranks++; files = 0; }
4183 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186 for(j = 0; j <i; j++) board_chars[j] = string[j];
4187 board_chars[i] = '\0';
4190 n = sscanf(string, PATTERN, &to_play, &double_push,
4191 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4192 &gamenum, white, black, &relation, &basetime, &increment,
4193 &white_stren, &black_stren, &white_time, &black_time,
4194 &moveNum, str, elapsed_time, move_str, &ics_flip,
4198 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4199 DisplayError(str, 0);
4203 /* Convert the move number to internal form */
4204 moveNum = (moveNum - 1) * 2;
4205 if (to_play == 'B') moveNum++;
4206 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4207 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4213 case RELATION_OBSERVING_PLAYED:
4214 case RELATION_OBSERVING_STATIC:
4215 if (gamenum == -1) {
4216 /* Old ICC buglet */
4217 relation = RELATION_OBSERVING_STATIC;
4219 newGameMode = IcsObserving;
4221 case RELATION_PLAYING_MYMOVE:
4222 case RELATION_PLAYING_NOTMYMOVE:
4224 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4225 IcsPlayingWhite : IcsPlayingBlack;
4226 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4228 case RELATION_EXAMINING:
4229 newGameMode = IcsExamining;
4231 case RELATION_ISOLATED_BOARD:
4233 /* Just display this board. If user was doing something else,
4234 we will forget about it until the next board comes. */
4235 newGameMode = IcsIdle;
4237 case RELATION_STARTING_POSITION:
4238 newGameMode = gameMode;
4242 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4243 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4244 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4246 for (k = 0; k < ranks; k++) {
4247 for (j = 0; j < files; j++)
4248 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4249 if(gameInfo.holdingsWidth > 1) {
4250 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4251 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4254 CopyBoard(partnerBoard, board);
4255 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4256 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4257 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4258 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4259 if(toSqr = strchr(str, '-')) {
4260 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4261 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4262 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4263 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4264 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4265 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4266 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4267 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4268 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4269 DisplayMessage(partnerStatus, "");
4270 partnerBoardValid = TRUE;
4274 /* Modify behavior for initial board display on move listing
4277 switch (ics_getting_history) {
4281 case H_GOT_REQ_HEADER:
4282 case H_GOT_UNREQ_HEADER:
4283 /* This is the initial position of the current game */
4284 gamenum = ics_gamenum;
4285 moveNum = 0; /* old ICS bug workaround */
4286 if (to_play == 'B') {
4287 startedFromSetupPosition = TRUE;
4288 blackPlaysFirst = TRUE;
4290 if (forwardMostMove == 0) forwardMostMove = 1;
4291 if (backwardMostMove == 0) backwardMostMove = 1;
4292 if (currentMove == 0) currentMove = 1;
4294 newGameMode = gameMode;
4295 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4297 case H_GOT_UNWANTED_HEADER:
4298 /* This is an initial board that we don't want */
4300 case H_GETTING_MOVES:
4301 /* Should not happen */
4302 DisplayError(_("Error gathering move list: extra board"), 0);
4303 ics_getting_history = H_FALSE;
4307 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4308 weird && (int)gameInfo.variant < (int)VariantShogi) {
4309 /* [HGM] We seem to have switched variant unexpectedly
4310 * Try to guess new variant from board size
4312 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4313 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4314 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4315 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4316 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4317 if(!weird) newVariant = VariantNormal;
4318 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4319 /* Get a move list just to see the header, which
4320 will tell us whether this is really bug or zh */
4321 if (ics_getting_history == H_FALSE) {
4322 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4323 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4328 /* Take action if this is the first board of a new game, or of a
4329 different game than is currently being displayed. */
4330 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4331 relation == RELATION_ISOLATED_BOARD) {
4333 /* Forget the old game and get the history (if any) of the new one */
4334 if (gameMode != BeginningOfGame) {
4338 if (appData.autoRaiseBoard) BoardToTop();
4340 if (gamenum == -1) {
4341 newGameMode = IcsIdle;
4342 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4343 appData.getMoveList && !reqFlag) {
4344 /* Need to get game history */
4345 ics_getting_history = H_REQUESTED;
4346 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4350 /* Initially flip the board to have black on the bottom if playing
4351 black or if the ICS flip flag is set, but let the user change
4352 it with the Flip View button. */
4353 flipView = appData.autoFlipView ?
4354 (newGameMode == IcsPlayingBlack) || ics_flip :
4357 /* Done with values from previous mode; copy in new ones */
4358 gameMode = newGameMode;
4360 ics_gamenum = gamenum;
4361 if (gamenum == gs_gamenum) {
4362 int klen = strlen(gs_kind);
4363 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4364 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4365 gameInfo.event = StrSave(str);
4367 gameInfo.event = StrSave("ICS game");
4369 gameInfo.site = StrSave(appData.icsHost);
4370 gameInfo.date = PGNDate();
4371 gameInfo.round = StrSave("-");
4372 gameInfo.white = StrSave(white);
4373 gameInfo.black = StrSave(black);
4374 timeControl = basetime * 60 * 1000;
4376 timeIncrement = increment * 1000;
4377 movesPerSession = 0;
4378 gameInfo.timeControl = TimeControlTagValue();
4379 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4380 if (appData.debugMode) {
4381 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4382 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4383 setbuf(debugFP, NULL);
4386 gameInfo.outOfBook = NULL;
4388 /* Do we have the ratings? */
4389 if (strcmp(player1Name, white) == 0 &&
4390 strcmp(player2Name, black) == 0) {
4391 if (appData.debugMode)
4392 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4393 player1Rating, player2Rating);
4394 gameInfo.whiteRating = player1Rating;
4395 gameInfo.blackRating = player2Rating;
4396 } else if (strcmp(player2Name, white) == 0 &&
4397 strcmp(player1Name, black) == 0) {
4398 if (appData.debugMode)
4399 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4400 player2Rating, player1Rating);
4401 gameInfo.whiteRating = player2Rating;
4402 gameInfo.blackRating = player1Rating;
4404 player1Name[0] = player2Name[0] = NULLCHAR;
4406 /* Silence shouts if requested */
4407 if (appData.quietPlay &&
4408 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4409 SendToICS(ics_prefix);
4410 SendToICS("set shout 0\n");
4414 /* Deal with midgame name changes */
4416 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4417 if (gameInfo.white) free(gameInfo.white);
4418 gameInfo.white = StrSave(white);
4420 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4421 if (gameInfo.black) free(gameInfo.black);
4422 gameInfo.black = StrSave(black);
4426 /* Throw away game result if anything actually changes in examine mode */
4427 if (gameMode == IcsExamining && !newGame) {
4428 gameInfo.result = GameUnfinished;
4429 if (gameInfo.resultDetails != NULL) {
4430 free(gameInfo.resultDetails);
4431 gameInfo.resultDetails = NULL;
4435 /* In pausing && IcsExamining mode, we ignore boards coming
4436 in if they are in a different variation than we are. */
4437 if (pauseExamInvalid) return;
4438 if (pausing && gameMode == IcsExamining) {
4439 if (moveNum <= pauseExamForwardMostMove) {
4440 pauseExamInvalid = TRUE;
4441 forwardMostMove = pauseExamForwardMostMove;
4446 if (appData.debugMode) {
4447 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4449 /* Parse the board */
4450 for (k = 0; k < ranks; k++) {
4451 for (j = 0; j < files; j++)
4452 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4453 if(gameInfo.holdingsWidth > 1) {
4454 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4455 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4458 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4459 board[5][BOARD_RGHT+1] = WhiteAngel;
4460 board[6][BOARD_RGHT+1] = WhiteMarshall;
4461 board[1][0] = BlackMarshall;
4462 board[2][0] = BlackAngel;
4463 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4465 CopyBoard(boards[moveNum], board);
4466 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4468 startedFromSetupPosition =
4469 !CompareBoards(board, initialPosition);
4470 if(startedFromSetupPosition)
4471 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4474 /* [HGM] Set castling rights. Take the outermost Rooks,
4475 to make it also work for FRC opening positions. Note that board12
4476 is really defective for later FRC positions, as it has no way to
4477 indicate which Rook can castle if they are on the same side of King.
4478 For the initial position we grant rights to the outermost Rooks,
4479 and remember thos rights, and we then copy them on positions
4480 later in an FRC game. This means WB might not recognize castlings with
4481 Rooks that have moved back to their original position as illegal,
4482 but in ICS mode that is not its job anyway.
4484 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4485 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4487 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4488 if(board[0][i] == WhiteRook) j = i;
4489 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4490 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4491 if(board[0][i] == WhiteRook) j = i;
4492 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4493 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4494 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4495 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4496 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4497 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4498 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4500 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4501 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4502 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4503 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4504 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4505 if(board[BOARD_HEIGHT-1][k] == bKing)
4506 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4507 if(gameInfo.variant == VariantTwoKings) {
4508 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4509 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4510 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4513 r = boards[moveNum][CASTLING][0] = initialRights[0];
4514 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4515 r = boards[moveNum][CASTLING][1] = initialRights[1];
4516 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4517 r = boards[moveNum][CASTLING][3] = initialRights[3];
4518 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4519 r = boards[moveNum][CASTLING][4] = initialRights[4];
4520 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4521 /* wildcastle kludge: always assume King has rights */
4522 r = boards[moveNum][CASTLING][2] = initialRights[2];
4523 r = boards[moveNum][CASTLING][5] = initialRights[5];
4525 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4526 boards[moveNum][EP_STATUS] = EP_NONE;
4527 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4528 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4529 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4532 if (ics_getting_history == H_GOT_REQ_HEADER ||
4533 ics_getting_history == H_GOT_UNREQ_HEADER) {
4534 /* This was an initial position from a move list, not
4535 the current position */
4539 /* Update currentMove and known move number limits */
4540 newMove = newGame || moveNum > forwardMostMove;
4543 forwardMostMove = backwardMostMove = currentMove = moveNum;
4544 if (gameMode == IcsExamining && moveNum == 0) {
4545 /* Workaround for ICS limitation: we are not told the wild
4546 type when starting to examine a game. But if we ask for
4547 the move list, the move list header will tell us */
4548 ics_getting_history = H_REQUESTED;
4549 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4553 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4555 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4556 /* [HGM] applied this also to an engine that is silently watching */
4557 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4558 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4559 gameInfo.variant == currentlyInitializedVariant) {
4560 takeback = forwardMostMove - moveNum;
4561 for (i = 0; i < takeback; i++) {
4562 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4563 SendToProgram("undo\n", &first);
4568 forwardMostMove = moveNum;
4569 if (!pausing || currentMove > forwardMostMove)
4570 currentMove = forwardMostMove;
4572 /* New part of history that is not contiguous with old part */
4573 if (pausing && gameMode == IcsExamining) {
4574 pauseExamInvalid = TRUE;
4575 forwardMostMove = pauseExamForwardMostMove;
4578 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4580 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4581 // [HGM] when we will receive the move list we now request, it will be
4582 // fed to the engine from the first move on. So if the engine is not
4583 // in the initial position now, bring it there.
4584 InitChessProgram(&first, 0);
4587 ics_getting_history = H_REQUESTED;
4588 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4591 forwardMostMove = backwardMostMove = currentMove = moveNum;
4594 /* Update the clocks */
4595 if (strchr(elapsed_time, '.')) {
4597 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4598 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4600 /* Time is in seconds */
4601 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4602 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4607 if (appData.zippyPlay && newGame &&
4608 gameMode != IcsObserving && gameMode != IcsIdle &&
4609 gameMode != IcsExamining)
4610 ZippyFirstBoard(moveNum, basetime, increment);
4613 /* Put the move on the move list, first converting
4614 to canonical algebraic form. */
4616 if (appData.debugMode) {
4617 if (appData.debugMode) { int f = forwardMostMove;
4618 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4619 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4620 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4622 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4623 fprintf(debugFP, "moveNum = %d\n", moveNum);
4624 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4625 setbuf(debugFP, NULL);
4627 if (moveNum <= backwardMostMove) {
4628 /* We don't know what the board looked like before
4630 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4631 strcat(parseList[moveNum - 1], " ");
4632 strcat(parseList[moveNum - 1], elapsed_time);
4633 moveList[moveNum - 1][0] = NULLCHAR;
4634 } else if (strcmp(move_str, "none") == 0) {
4635 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4636 /* Again, we don't know what the board looked like;
4637 this is really the start of the game. */
4638 parseList[moveNum - 1][0] = NULLCHAR;
4639 moveList[moveNum - 1][0] = NULLCHAR;
4640 backwardMostMove = moveNum;
4641 startedFromSetupPosition = TRUE;
4642 fromX = fromY = toX = toY = -1;
4644 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4645 // So we parse the long-algebraic move string in stead of the SAN move
4646 int valid; char buf[MSG_SIZ], *prom;
4648 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4649 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4650 // str looks something like "Q/a1-a2"; kill the slash
4652 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4653 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4654 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4655 strcat(buf, prom); // long move lacks promo specification!
4656 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4657 if(appData.debugMode)
4658 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4659 safeStrCpy(move_str, buf, MSG_SIZ);
4661 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4662 &fromX, &fromY, &toX, &toY, &promoChar)
4663 || ParseOneMove(buf, moveNum - 1, &moveType,
4664 &fromX, &fromY, &toX, &toY, &promoChar);
4665 // end of long SAN patch
4667 (void) CoordsToAlgebraic(boards[moveNum - 1],
4668 PosFlags(moveNum - 1),
4669 fromY, fromX, toY, toX, promoChar,
4670 parseList[moveNum-1]);
4671 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4677 if(gameInfo.variant != VariantShogi)
4678 strcat(parseList[moveNum - 1], "+");
4681 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4682 strcat(parseList[moveNum - 1], "#");
4685 strcat(parseList[moveNum - 1], " ");
4686 strcat(parseList[moveNum - 1], elapsed_time);
4687 /* currentMoveString is set as a side-effect of ParseOneMove */
4688 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4689 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4690 strcat(moveList[moveNum - 1], "\n");
4692 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4693 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4694 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4695 ChessSquare old, new = boards[moveNum][k][j];
4696 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4697 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4698 if(old == new) continue;
4699 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4700 else if(new == WhiteWazir || new == BlackWazir) {
4701 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4702 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4703 else boards[moveNum][k][j] = old; // preserve type of Gold
4704 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4705 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4708 /* Move from ICS was illegal!? Punt. */
4709 if (appData.debugMode) {
4710 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4711 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4713 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4714 strcat(parseList[moveNum - 1], " ");
4715 strcat(parseList[moveNum - 1], elapsed_time);
4716 moveList[moveNum - 1][0] = NULLCHAR;
4717 fromX = fromY = toX = toY = -1;
4720 if (appData.debugMode) {
4721 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4722 setbuf(debugFP, NULL);
4726 /* Send move to chess program (BEFORE animating it). */
4727 if (appData.zippyPlay && !newGame && newMove &&
4728 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4730 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4731 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4732 if (moveList[moveNum - 1][0] == NULLCHAR) {
4733 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4735 DisplayError(str, 0);
4737 if (first.sendTime) {
4738 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4740 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4741 if (firstMove && !bookHit) {
4743 if (first.useColors) {
4744 SendToProgram(gameMode == IcsPlayingWhite ?
4746 "black\ngo\n", &first);
4748 SendToProgram("go\n", &first);
4750 first.maybeThinking = TRUE;
4753 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4754 if (moveList[moveNum - 1][0] == NULLCHAR) {
4755 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4756 DisplayError(str, 0);
4758 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4759 SendMoveToProgram(moveNum - 1, &first);
4766 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4767 /* If move comes from a remote source, animate it. If it
4768 isn't remote, it will have already been animated. */
4769 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4770 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4772 if (!pausing && appData.highlightLastMove) {
4773 SetHighlights(fromX, fromY, toX, toY);
4777 /* Start the clocks */
4778 whiteFlag = blackFlag = FALSE;
4779 appData.clockMode = !(basetime == 0 && increment == 0);
4781 ics_clock_paused = TRUE;
4783 } else if (ticking == 1) {
4784 ics_clock_paused = FALSE;
4786 if (gameMode == IcsIdle ||
4787 relation == RELATION_OBSERVING_STATIC ||
4788 relation == RELATION_EXAMINING ||
4790 DisplayBothClocks();
4794 /* Display opponents and material strengths */
4795 if (gameInfo.variant != VariantBughouse &&
4796 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4797 if (tinyLayout || smallLayout) {
4798 if(gameInfo.variant == VariantNormal)
4799 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4800 gameInfo.white, white_stren, gameInfo.black, black_stren,
4801 basetime, increment);
4803 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4804 gameInfo.white, white_stren, gameInfo.black, black_stren,
4805 basetime, increment, (int) gameInfo.variant);
4807 if(gameInfo.variant == VariantNormal)
4808 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4809 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4810 basetime, increment);
4812 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4813 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4814 basetime, increment, VariantName(gameInfo.variant));
4817 if (appData.debugMode) {
4818 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4823 /* Display the board */
4824 if (!pausing && !appData.noGUI) {
4826 if (appData.premove)
4828 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4829 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4830 ClearPremoveHighlights();
4832 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4833 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4834 DrawPosition(j, boards[currentMove]);
4836 DisplayMove(moveNum - 1);
4837 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4838 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4839 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4840 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4844 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4846 if(bookHit) { // [HGM] book: simulate book reply
4847 static char bookMove[MSG_SIZ]; // a bit generous?
4849 programStats.nodes = programStats.depth = programStats.time =
4850 programStats.score = programStats.got_only_move = 0;
4851 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4853 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4854 strcat(bookMove, bookHit);
4855 HandleMachineMove(bookMove, &first);
4864 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4865 ics_getting_history = H_REQUESTED;
4866 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4872 AnalysisPeriodicEvent (int force)
4874 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4875 && !force) || !appData.periodicUpdates)
4878 /* Send . command to Crafty to collect stats */
4879 SendToProgram(".\n", &first);
4881 /* Don't send another until we get a response (this makes
4882 us stop sending to old Crafty's which don't understand
4883 the "." command (sending illegal cmds resets node count & time,
4884 which looks bad)) */
4885 programStats.ok_to_send = 0;
4889 ics_update_width (int new_width)
4891 ics_printf("set width %d\n", new_width);
4895 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4899 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4900 // null move in variant where engine does not understand it (for analysis purposes)
4901 SendBoard(cps, moveNum + 1); // send position after move in stead.
4904 if (cps->useUsermove) {
4905 SendToProgram("usermove ", cps);
4909 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4910 int len = space - parseList[moveNum];
4911 memcpy(buf, parseList[moveNum], len);
4913 buf[len] = NULLCHAR;
4915 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4917 SendToProgram(buf, cps);
4919 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4920 AlphaRank(moveList[moveNum], 4);
4921 SendToProgram(moveList[moveNum], cps);
4922 AlphaRank(moveList[moveNum], 4); // and back
4924 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4925 * the engine. It would be nice to have a better way to identify castle
4927 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4928 && cps->useOOCastle) {
4929 int fromX = moveList[moveNum][0] - AAA;
4930 int fromY = moveList[moveNum][1] - ONE;
4931 int toX = moveList[moveNum][2] - AAA;
4932 int toY = moveList[moveNum][3] - ONE;
4933 if((boards[moveNum][fromY][fromX] == WhiteKing
4934 && boards[moveNum][toY][toX] == WhiteRook)
4935 || (boards[moveNum][fromY][fromX] == BlackKing
4936 && boards[moveNum][toY][toX] == BlackRook)) {
4937 if(toX > fromX) SendToProgram("O-O\n", cps);
4938 else SendToProgram("O-O-O\n", cps);
4940 else SendToProgram(moveList[moveNum], cps);
4942 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4943 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4944 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4945 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4946 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4948 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4949 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4950 SendToProgram(buf, cps);
4952 else SendToProgram(moveList[moveNum], cps);
4953 /* End of additions by Tord */
4956 /* [HGM] setting up the opening has brought engine in force mode! */
4957 /* Send 'go' if we are in a mode where machine should play. */
4958 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4959 (gameMode == TwoMachinesPlay ||
4961 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4963 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4964 SendToProgram("go\n", cps);
4965 if (appData.debugMode) {
4966 fprintf(debugFP, "(extra)\n");
4969 setboardSpoiledMachineBlack = 0;
4973 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4975 char user_move[MSG_SIZ];
4978 if(gameInfo.variant == VariantSChess && promoChar) {
4979 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4980 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4981 } else suffix[0] = NULLCHAR;
4985 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4986 (int)moveType, fromX, fromY, toX, toY);
4987 DisplayError(user_move + strlen("say "), 0);
4989 case WhiteKingSideCastle:
4990 case BlackKingSideCastle:
4991 case WhiteQueenSideCastleWild:
4992 case BlackQueenSideCastleWild:
4994 case WhiteHSideCastleFR:
4995 case BlackHSideCastleFR:
4997 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4999 case WhiteQueenSideCastle:
5000 case BlackQueenSideCastle:
5001 case WhiteKingSideCastleWild:
5002 case BlackKingSideCastleWild:
5004 case WhiteASideCastleFR:
5005 case BlackASideCastleFR:
5007 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5009 case WhiteNonPromotion:
5010 case BlackNonPromotion:
5011 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5013 case WhitePromotion:
5014 case BlackPromotion:
5015 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5016 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5017 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5018 PieceToChar(WhiteFerz));
5019 else if(gameInfo.variant == VariantGreat)
5020 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5021 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5022 PieceToChar(WhiteMan));
5024 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5031 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5032 ToUpper(PieceToChar((ChessSquare) fromX)),
5033 AAA + toX, ONE + toY);
5035 case IllegalMove: /* could be a variant we don't quite understand */
5036 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5038 case WhiteCapturesEnPassant:
5039 case BlackCapturesEnPassant:
5040 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5041 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5044 SendToICS(user_move);
5045 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5046 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5051 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5052 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5053 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5054 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5055 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5058 if(gameMode != IcsExamining) { // is this ever not the case?
5059 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5061 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5062 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5063 } else { // on FICS we must first go to general examine mode
5064 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5066 if(gameInfo.variant != VariantNormal) {
5067 // try figure out wild number, as xboard names are not always valid on ICS
5068 for(i=1; i<=36; i++) {
5069 snprintf(buf, MSG_SIZ, "wild/%d", i);
5070 if(StringToVariant(buf) == gameInfo.variant) break;
5072 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5073 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5074 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5075 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5076 SendToICS(ics_prefix);
5078 if(startedFromSetupPosition || backwardMostMove != 0) {
5079 fen = PositionToFEN(backwardMostMove, NULL);
5080 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5081 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5083 } else { // FICS: everything has to set by separate bsetup commands
5084 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5085 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5087 if(!WhiteOnMove(backwardMostMove)) {
5088 SendToICS("bsetup tomove black\n");
5090 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5091 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5093 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5094 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5096 i = boards[backwardMostMove][EP_STATUS];
5097 if(i >= 0) { // set e.p.
5098 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5104 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5105 SendToICS("bsetup done\n"); // switch to normal examining.
5107 for(i = backwardMostMove; i<last; i++) {
5109 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5112 SendToICS(ics_prefix);
5113 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5117 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5119 if (rf == DROP_RANK) {
5120 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5121 sprintf(move, "%c@%c%c\n",
5122 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5124 if (promoChar == 'x' || promoChar == NULLCHAR) {
5125 sprintf(move, "%c%c%c%c\n",
5126 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5128 sprintf(move, "%c%c%c%c%c\n",
5129 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5135 ProcessICSInitScript (FILE *f)
5139 while (fgets(buf, MSG_SIZ, f)) {
5140 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5147 static int lastX, lastY, selectFlag, dragging;
5152 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5153 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5154 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5155 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5156 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5157 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5160 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5161 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5162 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5163 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5164 if(!step) step = -1;
5165 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5166 appData.testLegality && (promoSweep == king ||
5167 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5168 ChangeDragPiece(promoSweep);
5172 PromoScroll (int x, int y)
5176 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5177 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5178 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5179 if(!step) return FALSE;
5180 lastX = x; lastY = y;
5181 if((promoSweep < BlackPawn) == flipView) step = -step;
5182 if(step > 0) selectFlag = 1;
5183 if(!selectFlag) Sweep(step);
5188 NextPiece (int step)
5190 ChessSquare piece = boards[currentMove][toY][toX];
5193 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5194 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5195 if(!step) step = -1;
5196 } while(PieceToChar(pieceSweep) == '.');
5197 boards[currentMove][toY][toX] = pieceSweep;
5198 DrawPosition(FALSE, boards[currentMove]);
5199 boards[currentMove][toY][toX] = piece;
5201 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5203 AlphaRank (char *move, int n)
5205 // char *p = move, c; int x, y;
5207 if (appData.debugMode) {
5208 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5212 move[2]>='0' && move[2]<='9' &&
5213 move[3]>='a' && move[3]<='x' ) {
5215 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5216 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5218 if(move[0]>='0' && move[0]<='9' &&
5219 move[1]>='a' && move[1]<='x' &&
5220 move[2]>='0' && move[2]<='9' &&
5221 move[3]>='a' && move[3]<='x' ) {
5222 /* input move, Shogi -> normal */
5223 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5224 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5225 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5226 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5229 move[3]>='0' && move[3]<='9' &&
5230 move[2]>='a' && move[2]<='x' ) {
5232 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5233 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5236 move[0]>='a' && move[0]<='x' &&
5237 move[3]>='0' && move[3]<='9' &&
5238 move[2]>='a' && move[2]<='x' ) {
5239 /* output move, normal -> Shogi */
5240 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5241 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5242 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5243 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5244 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5246 if (appData.debugMode) {
5247 fprintf(debugFP, " out = '%s'\n", move);
5251 char yy_textstr[8000];
5253 /* Parser for moves from gnuchess, ICS, or user typein box */
5255 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5257 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5259 switch (*moveType) {
5260 case WhitePromotion:
5261 case BlackPromotion:
5262 case WhiteNonPromotion:
5263 case BlackNonPromotion:
5265 case WhiteCapturesEnPassant:
5266 case BlackCapturesEnPassant:
5267 case WhiteKingSideCastle:
5268 case WhiteQueenSideCastle:
5269 case BlackKingSideCastle:
5270 case BlackQueenSideCastle:
5271 case WhiteKingSideCastleWild:
5272 case WhiteQueenSideCastleWild:
5273 case BlackKingSideCastleWild:
5274 case BlackQueenSideCastleWild:
5275 /* Code added by Tord: */
5276 case WhiteHSideCastleFR:
5277 case WhiteASideCastleFR:
5278 case BlackHSideCastleFR:
5279 case BlackASideCastleFR:
5280 /* End of code added by Tord */
5281 case IllegalMove: /* bug or odd chess variant */
5282 *fromX = currentMoveString[0] - AAA;
5283 *fromY = currentMoveString[1] - ONE;
5284 *toX = currentMoveString[2] - AAA;
5285 *toY = currentMoveString[3] - ONE;
5286 *promoChar = currentMoveString[4];
5287 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5288 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5289 if (appData.debugMode) {
5290 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5292 *fromX = *fromY = *toX = *toY = 0;
5295 if (appData.testLegality) {
5296 return (*moveType != IllegalMove);
5298 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5299 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5304 *fromX = *moveType == WhiteDrop ?
5305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5306 (int) CharToPiece(ToLower(currentMoveString[0]));
5308 *toX = currentMoveString[2] - AAA;
5309 *toY = currentMoveString[3] - ONE;
5310 *promoChar = NULLCHAR;
5314 case ImpossibleMove:
5324 if (appData.debugMode) {
5325 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5328 *fromX = *fromY = *toX = *toY = 0;
5329 *promoChar = NULLCHAR;
5334 Boolean pushed = FALSE;
5335 char *lastParseAttempt;
5338 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5339 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5340 int fromX, fromY, toX, toY; char promoChar;
5345 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5346 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5349 endPV = forwardMostMove;
5351 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5352 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5353 lastParseAttempt = pv;
5354 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5355 if(!valid && nr == 0 &&
5356 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5357 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5358 // Hande case where played move is different from leading PV move
5359 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5360 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5361 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5362 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5363 endPV += 2; // if position different, keep this
5364 moveList[endPV-1][0] = fromX + AAA;
5365 moveList[endPV-1][1] = fromY + ONE;
5366 moveList[endPV-1][2] = toX + AAA;
5367 moveList[endPV-1][3] = toY + ONE;
5368 parseList[endPV-1][0] = NULLCHAR;
5369 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5372 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5373 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5374 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5375 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5376 valid++; // allow comments in PV
5380 if(endPV+1 > framePtr) break; // no space, truncate
5383 CopyBoard(boards[endPV], boards[endPV-1]);
5384 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5385 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5386 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5387 CoordsToAlgebraic(boards[endPV - 1],
5388 PosFlags(endPV - 1),
5389 fromY, fromX, toY, toX, promoChar,
5390 parseList[endPV - 1]);
5392 if(atEnd == 2) return; // used hidden, for PV conversion
5393 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5394 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5395 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5396 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5397 DrawPosition(TRUE, boards[currentMove]);
5401 MultiPV (ChessProgramState *cps)
5402 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5404 for(i=0; i<cps->nrOptions; i++)
5405 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5411 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5413 int startPV, multi, lineStart, origIndex = index;
5414 char *p, buf2[MSG_SIZ];
5416 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5417 lastX = x; lastY = y;
5418 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5419 lineStart = startPV = index;
5420 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5421 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5423 do{ while(buf[index] && buf[index] != '\n') index++;
5424 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5426 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5427 int n = first.option[multi].value;
5428 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5429 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5430 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5431 first.option[multi].value = n;
5434 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5435 ExcludeClick(origIndex - lineStart);
5438 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5439 *start = startPV; *end = index-1;
5446 static char buf[10*MSG_SIZ];
5447 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5449 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5450 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5451 for(i = forwardMostMove; i<endPV; i++){
5452 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5453 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5456 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5457 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5463 LoadPV (int x, int y)
5464 { // called on right mouse click to load PV
5465 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5466 lastX = x; lastY = y;
5467 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5474 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5475 if(endPV < 0) return;
5476 if(appData.autoCopyPV) CopyFENToClipboard();
5478 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5479 Boolean saveAnimate = appData.animate;
5481 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5482 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5483 } else storedGames--; // abandon shelved tail of original game
5486 forwardMostMove = currentMove;
5487 currentMove = oldFMM;
5488 appData.animate = FALSE;
5489 ToNrEvent(forwardMostMove);
5490 appData.animate = saveAnimate;
5492 currentMove = forwardMostMove;
5493 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5494 ClearPremoveHighlights();
5495 DrawPosition(TRUE, boards[currentMove]);
5499 MovePV (int x, int y, int h)
5500 { // step through PV based on mouse coordinates (called on mouse move)
5501 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5503 // we must somehow check if right button is still down (might be released off board!)
5504 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5505 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5506 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5508 lastX = x; lastY = y;
5510 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5511 if(endPV < 0) return;
5512 if(y < margin) step = 1; else
5513 if(y > h - margin) step = -1;
5514 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5515 currentMove += step;
5516 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5517 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5518 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5519 DrawPosition(FALSE, boards[currentMove]);
5523 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5524 // All positions will have equal probability, but the current method will not provide a unique
5525 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5531 int piecesLeft[(int)BlackPawn];
5532 int seed, nrOfShuffles;
5535 GetPositionNumber ()
5536 { // sets global variable seed
5539 seed = appData.defaultFrcPosition;
5540 if(seed < 0) { // randomize based on time for negative FRC position numbers
5541 for(i=0; i<50; i++) seed += random();
5542 seed = random() ^ random() >> 8 ^ random() << 8;
5543 if(seed<0) seed = -seed;
5548 put (Board board, int pieceType, int rank, int n, int shade)
5549 // put the piece on the (n-1)-th empty squares of the given shade
5553 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5554 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5555 board[rank][i] = (ChessSquare) pieceType;
5556 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5558 piecesLeft[pieceType]--;
5567 AddOnePiece (Board board, int pieceType, int rank, int shade)
5568 // calculate where the next piece goes, (any empty square), and put it there
5572 i = seed % squaresLeft[shade];
5573 nrOfShuffles *= squaresLeft[shade];
5574 seed /= squaresLeft[shade];
5575 put(board, pieceType, rank, i, shade);
5579 AddTwoPieces (Board board, int pieceType, int rank)
5580 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5582 int i, n=squaresLeft[ANY], j=n-1, k;
5584 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5585 i = seed % k; // pick one
5588 while(i >= j) i -= j--;
5589 j = n - 1 - j; i += j;
5590 put(board, pieceType, rank, j, ANY);
5591 put(board, pieceType, rank, i, ANY);
5595 SetUpShuffle (Board board, int number)
5599 GetPositionNumber(); nrOfShuffles = 1;
5601 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5602 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5603 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5605 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5607 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5608 p = (int) board[0][i];
5609 if(p < (int) BlackPawn) piecesLeft[p] ++;
5610 board[0][i] = EmptySquare;
5613 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5614 // shuffles restricted to allow normal castling put KRR first
5615 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5616 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5617 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5618 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5619 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5620 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5621 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5622 put(board, WhiteRook, 0, 0, ANY);
5623 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5626 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5627 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5628 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5629 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5630 while(piecesLeft[p] >= 2) {
5631 AddOnePiece(board, p, 0, LITE);
5632 AddOnePiece(board, p, 0, DARK);
5634 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5637 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5638 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5639 // but we leave King and Rooks for last, to possibly obey FRC restriction
5640 if(p == (int)WhiteRook) continue;
5641 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5642 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5645 // now everything is placed, except perhaps King (Unicorn) and Rooks
5647 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5648 // Last King gets castling rights
5649 while(piecesLeft[(int)WhiteUnicorn]) {
5650 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5651 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5654 while(piecesLeft[(int)WhiteKing]) {
5655 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5661 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5662 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5665 // Only Rooks can be left; simply place them all
5666 while(piecesLeft[(int)WhiteRook]) {
5667 i = put(board, WhiteRook, 0, 0, ANY);
5668 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5671 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5673 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5676 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5677 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5680 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5684 SetCharTable (char *table, const char * map)
5685 /* [HGM] moved here from winboard.c because of its general usefulness */
5686 /* Basically a safe strcpy that uses the last character as King */
5688 int result = FALSE; int NrPieces;
5690 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5691 && NrPieces >= 12 && !(NrPieces&1)) {
5692 int i; /* [HGM] Accept even length from 12 to 34 */
5694 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5695 for( i=0; i<NrPieces/2-1; i++ ) {
5697 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5699 table[(int) WhiteKing] = map[NrPieces/2-1];
5700 table[(int) BlackKing] = map[NrPieces-1];
5709 Prelude (Board board)
5710 { // [HGM] superchess: random selection of exo-pieces
5711 int i, j, k; ChessSquare p;
5712 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5714 GetPositionNumber(); // use FRC position number
5716 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5717 SetCharTable(pieceToChar, appData.pieceToCharTable);
5718 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5719 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5722 j = seed%4; seed /= 4;
5723 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5724 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5725 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5726 j = seed%3 + (seed%3 >= j); seed /= 3;
5727 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5728 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5729 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5730 j = seed%3; seed /= 3;
5731 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5732 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5733 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5734 j = seed%2 + (seed%2 >= j); seed /= 2;
5735 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5736 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5737 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5738 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5739 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5740 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5741 put(board, exoPieces[0], 0, 0, ANY);
5742 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5746 InitPosition (int redraw)
5748 ChessSquare (* pieces)[BOARD_FILES];
5749 int i, j, pawnRow, overrule,
5750 oldx = gameInfo.boardWidth,
5751 oldy = gameInfo.boardHeight,
5752 oldh = gameInfo.holdingsWidth;
5755 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5757 /* [AS] Initialize pv info list [HGM] and game status */
5759 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5760 pvInfoList[i].depth = 0;
5761 boards[i][EP_STATUS] = EP_NONE;
5762 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5765 initialRulePlies = 0; /* 50-move counter start */
5767 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5768 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5772 /* [HGM] logic here is completely changed. In stead of full positions */
5773 /* the initialized data only consist of the two backranks. The switch */
5774 /* selects which one we will use, which is than copied to the Board */
5775 /* initialPosition, which for the rest is initialized by Pawns and */
5776 /* empty squares. This initial position is then copied to boards[0], */
5777 /* possibly after shuffling, so that it remains available. */
5779 gameInfo.holdingsWidth = 0; /* default board sizes */
5780 gameInfo.boardWidth = 8;
5781 gameInfo.boardHeight = 8;
5782 gameInfo.holdingsSize = 0;
5783 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5784 for(i=0; i<BOARD_FILES-2; i++)
5785 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5786 initialPosition[EP_STATUS] = EP_NONE;
5787 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5788 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5789 SetCharTable(pieceNickName, appData.pieceNickNames);
5790 else SetCharTable(pieceNickName, "............");
5793 switch (gameInfo.variant) {
5794 case VariantFischeRandom:
5795 shuffleOpenings = TRUE;
5798 case VariantShatranj:
5799 pieces = ShatranjArray;
5800 nrCastlingRights = 0;
5801 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5804 pieces = makrukArray;
5805 nrCastlingRights = 0;
5806 startedFromSetupPosition = TRUE;
5807 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5809 case VariantTwoKings:
5810 pieces = twoKingsArray;
5813 pieces = GrandArray;
5814 nrCastlingRights = 0;
5815 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5816 gameInfo.boardWidth = 10;
5817 gameInfo.boardHeight = 10;
5818 gameInfo.holdingsSize = 7;
5820 case VariantCapaRandom:
5821 shuffleOpenings = TRUE;
5822 case VariantCapablanca:
5823 pieces = CapablancaArray;
5824 gameInfo.boardWidth = 10;
5825 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5828 pieces = GothicArray;
5829 gameInfo.boardWidth = 10;
5830 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5833 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5834 gameInfo.holdingsSize = 7;
5837 pieces = JanusArray;
5838 gameInfo.boardWidth = 10;
5839 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5840 nrCastlingRights = 6;
5841 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5842 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5843 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5844 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5845 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5846 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5849 pieces = FalconArray;
5850 gameInfo.boardWidth = 10;
5851 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5853 case VariantXiangqi:
5854 pieces = XiangqiArray;
5855 gameInfo.boardWidth = 9;
5856 gameInfo.boardHeight = 10;
5857 nrCastlingRights = 0;
5858 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5861 pieces = ShogiArray;
5862 gameInfo.boardWidth = 9;
5863 gameInfo.boardHeight = 9;
5864 gameInfo.holdingsSize = 7;
5865 nrCastlingRights = 0;
5866 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5868 case VariantCourier:
5869 pieces = CourierArray;
5870 gameInfo.boardWidth = 12;
5871 nrCastlingRights = 0;
5872 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5874 case VariantKnightmate:
5875 pieces = KnightmateArray;
5876 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5878 case VariantSpartan:
5879 pieces = SpartanArray;
5880 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5883 pieces = fairyArray;
5884 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5887 pieces = GreatArray;
5888 gameInfo.boardWidth = 10;
5889 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5890 gameInfo.holdingsSize = 8;
5894 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5895 gameInfo.holdingsSize = 8;
5896 startedFromSetupPosition = TRUE;
5898 case VariantCrazyhouse:
5899 case VariantBughouse:
5901 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5902 gameInfo.holdingsSize = 5;
5904 case VariantWildCastle:
5906 /* !!?shuffle with kings guaranteed to be on d or e file */
5907 shuffleOpenings = 1;
5909 case VariantNoCastle:
5911 nrCastlingRights = 0;
5912 /* !!?unconstrained back-rank shuffle */
5913 shuffleOpenings = 1;
5918 if(appData.NrFiles >= 0) {
5919 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5920 gameInfo.boardWidth = appData.NrFiles;
5922 if(appData.NrRanks >= 0) {
5923 gameInfo.boardHeight = appData.NrRanks;
5925 if(appData.holdingsSize >= 0) {
5926 i = appData.holdingsSize;
5927 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5928 gameInfo.holdingsSize = i;
5930 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5931 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5932 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5934 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5935 if(pawnRow < 1) pawnRow = 1;
5936 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5938 /* User pieceToChar list overrules defaults */
5939 if(appData.pieceToCharTable != NULL)
5940 SetCharTable(pieceToChar, appData.pieceToCharTable);
5942 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5944 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5945 s = (ChessSquare) 0; /* account holding counts in guard band */
5946 for( i=0; i<BOARD_HEIGHT; i++ )
5947 initialPosition[i][j] = s;
5949 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5950 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5951 initialPosition[pawnRow][j] = WhitePawn;
5952 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5953 if(gameInfo.variant == VariantXiangqi) {
5955 initialPosition[pawnRow][j] =
5956 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5957 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5958 initialPosition[2][j] = WhiteCannon;
5959 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5963 if(gameInfo.variant == VariantGrand) {
5964 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5965 initialPosition[0][j] = WhiteRook;
5966 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5969 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5971 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5974 initialPosition[1][j] = WhiteBishop;
5975 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5977 initialPosition[1][j] = WhiteRook;
5978 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5981 if( nrCastlingRights == -1) {
5982 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5983 /* This sets default castling rights from none to normal corners */
5984 /* Variants with other castling rights must set them themselves above */
5985 nrCastlingRights = 6;
5987 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5988 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5989 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5990 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5991 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5992 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5995 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5996 if(gameInfo.variant == VariantGreat) { // promotion commoners
5997 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5998 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5999 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6000 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6002 if( gameInfo.variant == VariantSChess ) {
6003 initialPosition[1][0] = BlackMarshall;
6004 initialPosition[2][0] = BlackAngel;
6005 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6006 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6007 initialPosition[1][1] = initialPosition[2][1] =
6008 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6010 if (appData.debugMode) {
6011 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6013 if(shuffleOpenings) {
6014 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6015 startedFromSetupPosition = TRUE;
6017 if(startedFromPositionFile) {
6018 /* [HGM] loadPos: use PositionFile for every new game */
6019 CopyBoard(initialPosition, filePosition);
6020 for(i=0; i<nrCastlingRights; i++)
6021 initialRights[i] = filePosition[CASTLING][i];
6022 startedFromSetupPosition = TRUE;
6025 CopyBoard(boards[0], initialPosition);
6027 if(oldx != gameInfo.boardWidth ||
6028 oldy != gameInfo.boardHeight ||
6029 oldv != gameInfo.variant ||
6030 oldh != gameInfo.holdingsWidth
6032 InitDrawingSizes(-2 ,0);
6034 oldv = gameInfo.variant;
6036 DrawPosition(TRUE, boards[currentMove]);
6040 SendBoard (ChessProgramState *cps, int moveNum)
6042 char message[MSG_SIZ];
6044 if (cps->useSetboard) {
6045 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6046 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6047 SendToProgram(message, cps);
6052 int i, j, left=0, right=BOARD_WIDTH;
6053 /* Kludge to set black to move, avoiding the troublesome and now
6054 * deprecated "black" command.
6056 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6057 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6059 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6061 SendToProgram("edit\n", cps);
6062 SendToProgram("#\n", cps);
6063 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6064 bp = &boards[moveNum][i][left];
6065 for (j = left; j < right; j++, bp++) {
6066 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6067 if ((int) *bp < (int) BlackPawn) {
6068 if(j == BOARD_RGHT+1)
6069 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6070 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6071 if(message[0] == '+' || message[0] == '~') {
6072 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6073 PieceToChar((ChessSquare)(DEMOTED *bp)),
6076 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6077 message[1] = BOARD_RGHT - 1 - j + '1';
6078 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6080 SendToProgram(message, cps);
6085 SendToProgram("c\n", cps);
6086 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6087 bp = &boards[moveNum][i][left];
6088 for (j = left; j < right; j++, bp++) {
6089 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6090 if (((int) *bp != (int) EmptySquare)
6091 && ((int) *bp >= (int) BlackPawn)) {
6092 if(j == BOARD_LEFT-2)
6093 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6094 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6096 if(message[0] == '+' || message[0] == '~') {
6097 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6098 PieceToChar((ChessSquare)(DEMOTED *bp)),
6101 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6102 message[1] = BOARD_RGHT - 1 - j + '1';
6103 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6105 SendToProgram(message, cps);
6110 SendToProgram(".\n", cps);
6112 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6115 char exclusionHeader[MSG_SIZ];
6116 int exCnt, excludePtr;
6117 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6118 static Exclusion excluTab[200];
6119 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6125 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6126 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6133 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6134 excludePtr = 24; exCnt = 0;
6139 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6140 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6141 char buf[2*MOVE_LEN], *p;
6142 Exclusion *e = excluTab;
6144 for(i=0; i<exCnt; i++)
6145 if(e[i].ff == fromX && e[i].fr == fromY &&
6146 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6147 if(i == exCnt) { // was not in exclude list; add it
6148 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6149 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6150 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6153 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6154 excludePtr++; e[i].mark = excludePtr++;
6155 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6158 exclusionHeader[e[i].mark] = state;
6162 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6163 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6164 char *p, buf[MSG_SIZ];
6167 if(promoChar == -1) { // kludge to indicate best move
6168 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6169 return 1; // if unparsable, abort
6171 // update exclusion map (resolving toggle by consulting existing state)
6172 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6174 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6175 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6176 excludeMap[k] |= 1<<j;
6177 else excludeMap[k] &= ~(1<<j);
6179 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6181 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6182 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6183 SendToProgram(buf, &first);
6184 return (state == '+');
6188 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 skip = !all && group[0]; // if group requested, we start in skip mode
10034 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10035 p = names; q = buf; header = 0;
10036 while(*p && *p != '\n') *q++ = *p++;
10038 if(*p == '\n') p++;
10039 if(buf[0] == '#') {
10040 if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10041 depth++; // we must be entering a new group
10042 if(all) continue; // suppress printing group headers when complete list requested
10044 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10046 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10047 if(engineList[i]) free(engineList[i]);
10048 engineList[i] = strdup(buf);
10049 if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10050 if(engineMnemonic[i]) free(engineMnemonic[i]);
10051 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10053 sscanf(q + 8, "%s", buf + strlen(buf));
10056 engineMnemonic[i] = strdup(buf);
10059 engineList[i] = engineMnemonic[i] = NULL;
10063 // following implemented as macro to avoid type limitations
10064 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10067 SwapEngines (int n)
10068 { // swap settings for first engine and other engine (so far only some selected options)
10073 SWAP(chessProgram, p)
10075 SWAP(hasOwnBookUCI, h)
10076 SWAP(protocolVersion, h)
10078 SWAP(scoreIsAbsolute, h)
10083 SWAP(engOptions, p)
10084 SWAP(engInitString, p)
10085 SWAP(computerString, p)
10087 SWAP(fenOverride, p)
10089 SWAP(accumulateTC, h)
10094 SetPlayer (int player, char *p)
10095 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10097 char buf[MSG_SIZ], *engineName;
10098 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10099 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10100 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10102 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10103 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10104 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10105 ParseArgsFromString(buf);
10111 char *recentEngines;
10114 RecentEngineEvent (int nr)
10117 // SwapEngines(1); // bump first to second
10118 // ReplaceEngine(&second, 1); // and load it there
10119 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10120 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10121 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10122 ReplaceEngine(&first, 0);
10123 FloatToFront(&appData.recentEngineList, command[n]);
10128 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10129 { // determine players from game number
10130 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10132 if(appData.tourneyType == 0) {
10133 roundsPerCycle = (nPlayers - 1) | 1;
10134 pairingsPerRound = nPlayers / 2;
10135 } else if(appData.tourneyType > 0) {
10136 roundsPerCycle = nPlayers - appData.tourneyType;
10137 pairingsPerRound = appData.tourneyType;
10139 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10140 gamesPerCycle = gamesPerRound * roundsPerCycle;
10141 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10142 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10143 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10144 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10145 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10146 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10148 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10149 if(appData.roundSync) *syncInterval = gamesPerRound;
10151 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10153 if(appData.tourneyType == 0) {
10154 if(curPairing == (nPlayers-1)/2 ) {
10155 *whitePlayer = curRound;
10156 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10158 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10159 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10160 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10161 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10163 } else if(appData.tourneyType > 1) {
10164 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10165 *whitePlayer = curRound + appData.tourneyType;
10166 } else if(appData.tourneyType > 0) {
10167 *whitePlayer = curPairing;
10168 *blackPlayer = curRound + appData.tourneyType;
10171 // take care of white/black alternation per round.
10172 // For cycles and games this is already taken care of by default, derived from matchGame!
10173 return curRound & 1;
10177 NextTourneyGame (int nr, int *swapColors)
10178 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10180 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10182 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10183 tf = fopen(appData.tourneyFile, "r");
10184 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10185 ParseArgsFromFile(tf); fclose(tf);
10186 InitTimeControls(); // TC might be altered from tourney file
10188 nPlayers = CountPlayers(appData.participants); // count participants
10189 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10190 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10193 p = q = appData.results;
10194 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10195 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10196 DisplayMessage(_("Waiting for other game(s)"),"");
10197 waitingForGame = TRUE;
10198 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10201 waitingForGame = FALSE;
10204 if(appData.tourneyType < 0) {
10205 if(nr>=0 && !pairingReceived) {
10207 if(pairing.pr == NoProc) {
10208 if(!appData.pairingEngine[0]) {
10209 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10212 StartChessProgram(&pairing); // starts the pairing engine
10214 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10215 SendToProgram(buf, &pairing);
10216 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10217 SendToProgram(buf, &pairing);
10218 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10220 pairingReceived = 0; // ... so we continue here
10222 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10223 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10224 matchGame = 1; roundNr = nr / syncInterval + 1;
10227 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10229 // redefine engines, engine dir, etc.
10230 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10231 if(first.pr == NoProc) {
10232 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10233 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10235 if(second.pr == NoProc) {
10237 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10238 SwapEngines(1); // and make that valid for second engine by swapping
10239 InitEngine(&second, 1);
10241 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10242 UpdateLogos(FALSE); // leave display to ModeHiglight()
10248 { // performs game initialization that does not invoke engines, and then tries to start the game
10249 int res, firstWhite, swapColors = 0;
10250 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10251 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
10253 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10254 if(strcmp(buf, currentDebugFile)) { // name has changed
10255 FILE *f = fopen(buf, "w");
10256 if(f) { // if opening the new file failed, just keep using the old one
10257 ASSIGN(currentDebugFile, buf);
10261 if(appData.serverFileName) {
10262 if(serverFP) fclose(serverFP);
10263 serverFP = fopen(appData.serverFileName, "w");
10264 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10265 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10269 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10270 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10271 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10272 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10273 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10274 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10275 Reset(FALSE, first.pr != NoProc);
10276 res = LoadGameOrPosition(matchGame); // setup game
10277 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10278 if(!res) return; // abort when bad game/pos file
10279 TwoMachinesEvent();
10283 UserAdjudicationEvent (int result)
10285 ChessMove gameResult = GameIsDrawn;
10288 gameResult = WhiteWins;
10290 else if( result < 0 ) {
10291 gameResult = BlackWins;
10294 if( gameMode == TwoMachinesPlay ) {
10295 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10300 // [HGM] save: calculate checksum of game to make games easily identifiable
10302 StringCheckSum (char *s)
10305 if(s==NULL) return 0;
10306 while(*s) i = i*259 + *s++;
10314 for(i=backwardMostMove; i<forwardMostMove; i++) {
10315 sum += pvInfoList[i].depth;
10316 sum += StringCheckSum(parseList[i]);
10317 sum += StringCheckSum(commentList[i]);
10320 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10321 return sum + StringCheckSum(commentList[i]);
10322 } // end of save patch
10325 GameEnds (ChessMove result, char *resultDetails, int whosays)
10327 GameMode nextGameMode;
10329 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10331 if(endingGame) return; /* [HGM] crash: forbid recursion */
10333 if(twoBoards) { // [HGM] dual: switch back to one board
10334 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10335 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10337 if (appData.debugMode) {
10338 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10339 result, resultDetails ? resultDetails : "(null)", whosays);
10342 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10344 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10345 /* If we are playing on ICS, the server decides when the
10346 game is over, but the engine can offer to draw, claim
10350 if (appData.zippyPlay && first.initDone) {
10351 if (result == GameIsDrawn) {
10352 /* In case draw still needs to be claimed */
10353 SendToICS(ics_prefix);
10354 SendToICS("draw\n");
10355 } else if (StrCaseStr(resultDetails, "resign")) {
10356 SendToICS(ics_prefix);
10357 SendToICS("resign\n");
10361 endingGame = 0; /* [HGM] crash */
10365 /* If we're loading the game from a file, stop */
10366 if (whosays == GE_FILE) {
10367 (void) StopLoadGameTimer();
10371 /* Cancel draw offers */
10372 first.offeredDraw = second.offeredDraw = 0;
10374 /* If this is an ICS game, only ICS can really say it's done;
10375 if not, anyone can. */
10376 isIcsGame = (gameMode == IcsPlayingWhite ||
10377 gameMode == IcsPlayingBlack ||
10378 gameMode == IcsObserving ||
10379 gameMode == IcsExamining);
10381 if (!isIcsGame || whosays == GE_ICS) {
10382 /* OK -- not an ICS game, or ICS said it was done */
10384 if (!isIcsGame && !appData.noChessProgram)
10385 SetUserThinkingEnables();
10387 /* [HGM] if a machine claims the game end we verify this claim */
10388 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10389 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10391 ChessMove trueResult = (ChessMove) -1;
10393 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10394 first.twoMachinesColor[0] :
10395 second.twoMachinesColor[0] ;
10397 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10398 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10399 /* [HGM] verify: engine mate claims accepted if they were flagged */
10400 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10402 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10403 /* [HGM] verify: engine mate claims accepted if they were flagged */
10404 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10406 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10407 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10410 // now verify win claims, but not in drop games, as we don't understand those yet
10411 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10412 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10413 (result == WhiteWins && claimer == 'w' ||
10414 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10415 if (appData.debugMode) {
10416 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10417 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10419 if(result != trueResult) {
10420 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10421 result = claimer == 'w' ? BlackWins : WhiteWins;
10422 resultDetails = buf;
10425 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10426 && (forwardMostMove <= backwardMostMove ||
10427 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10428 (claimer=='b')==(forwardMostMove&1))
10430 /* [HGM] verify: draws that were not flagged are false claims */
10431 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10432 result = claimer == 'w' ? BlackWins : WhiteWins;
10433 resultDetails = buf;
10435 /* (Claiming a loss is accepted no questions asked!) */
10437 /* [HGM] bare: don't allow bare King to win */
10438 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10439 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10440 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10441 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10442 && result != GameIsDrawn)
10443 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10444 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10445 int p = (signed char)boards[forwardMostMove][i][j] - color;
10446 if(p >= 0 && p <= (int)WhiteKing) k++;
10448 if (appData.debugMode) {
10449 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10450 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10453 result = GameIsDrawn;
10454 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10455 resultDetails = buf;
10461 if(serverMoves != NULL && !loadFlag) { char c = '=';
10462 if(result==WhiteWins) c = '+';
10463 if(result==BlackWins) c = '-';
10464 if(resultDetails != NULL)
10465 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10467 if (resultDetails != NULL) {
10468 gameInfo.result = result;
10469 gameInfo.resultDetails = StrSave(resultDetails);
10471 /* display last move only if game was not loaded from file */
10472 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10473 DisplayMove(currentMove - 1);
10475 if (forwardMostMove != 0) {
10476 if (gameMode != PlayFromGameFile && gameMode != EditGame
10477 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10479 if (*appData.saveGameFile != NULLCHAR) {
10480 SaveGameToFile(appData.saveGameFile, TRUE);
10481 } else if (appData.autoSaveGames) {
10484 if (*appData.savePositionFile != NULLCHAR) {
10485 SavePositionToFile(appData.savePositionFile);
10490 /* Tell program how game ended in case it is learning */
10491 /* [HGM] Moved this to after saving the PGN, just in case */
10492 /* engine died and we got here through time loss. In that */
10493 /* case we will get a fatal error writing the pipe, which */
10494 /* would otherwise lose us the PGN. */
10495 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10496 /* output during GameEnds should never be fatal anymore */
10497 if (gameMode == MachinePlaysWhite ||
10498 gameMode == MachinePlaysBlack ||
10499 gameMode == TwoMachinesPlay ||
10500 gameMode == IcsPlayingWhite ||
10501 gameMode == IcsPlayingBlack ||
10502 gameMode == BeginningOfGame) {
10504 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10506 if (first.pr != NoProc) {
10507 SendToProgram(buf, &first);
10509 if (second.pr != NoProc &&
10510 gameMode == TwoMachinesPlay) {
10511 SendToProgram(buf, &second);
10516 if (appData.icsActive) {
10517 if (appData.quietPlay &&
10518 (gameMode == IcsPlayingWhite ||
10519 gameMode == IcsPlayingBlack)) {
10520 SendToICS(ics_prefix);
10521 SendToICS("set shout 1\n");
10523 nextGameMode = IcsIdle;
10524 ics_user_moved = FALSE;
10525 /* clean up premove. It's ugly when the game has ended and the
10526 * premove highlights are still on the board.
10529 gotPremove = FALSE;
10530 ClearPremoveHighlights();
10531 DrawPosition(FALSE, boards[currentMove]);
10533 if (whosays == GE_ICS) {
10536 if (gameMode == IcsPlayingWhite)
10538 else if(gameMode == IcsPlayingBlack)
10539 PlayIcsLossSound();
10542 if (gameMode == IcsPlayingBlack)
10544 else if(gameMode == IcsPlayingWhite)
10545 PlayIcsLossSound();
10548 PlayIcsDrawSound();
10551 PlayIcsUnfinishedSound();
10554 } else if (gameMode == EditGame ||
10555 gameMode == PlayFromGameFile ||
10556 gameMode == AnalyzeMode ||
10557 gameMode == AnalyzeFile) {
10558 nextGameMode = gameMode;
10560 nextGameMode = EndOfGame;
10565 nextGameMode = gameMode;
10568 if (appData.noChessProgram) {
10569 gameMode = nextGameMode;
10571 endingGame = 0; /* [HGM] crash */
10576 /* Put first chess program into idle state */
10577 if (first.pr != NoProc &&
10578 (gameMode == MachinePlaysWhite ||
10579 gameMode == MachinePlaysBlack ||
10580 gameMode == TwoMachinesPlay ||
10581 gameMode == IcsPlayingWhite ||
10582 gameMode == IcsPlayingBlack ||
10583 gameMode == BeginningOfGame)) {
10584 SendToProgram("force\n", &first);
10585 if (first.usePing) {
10587 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10588 SendToProgram(buf, &first);
10591 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10592 /* Kill off first chess program */
10593 if (first.isr != NULL)
10594 RemoveInputSource(first.isr);
10597 if (first.pr != NoProc) {
10599 DoSleep( appData.delayBeforeQuit );
10600 SendToProgram("quit\n", &first);
10601 DoSleep( appData.delayAfterQuit );
10602 DestroyChildProcess(first.pr, first.useSigterm);
10606 if (second.reuse) {
10607 /* Put second chess program into idle state */
10608 if (second.pr != NoProc &&
10609 gameMode == TwoMachinesPlay) {
10610 SendToProgram("force\n", &second);
10611 if (second.usePing) {
10613 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10614 SendToProgram(buf, &second);
10617 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10618 /* Kill off second chess program */
10619 if (second.isr != NULL)
10620 RemoveInputSource(second.isr);
10623 if (second.pr != NoProc) {
10624 DoSleep( appData.delayBeforeQuit );
10625 SendToProgram("quit\n", &second);
10626 DoSleep( appData.delayAfterQuit );
10627 DestroyChildProcess(second.pr, second.useSigterm);
10629 second.pr = NoProc;
10632 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10633 char resChar = '=';
10637 if (first.twoMachinesColor[0] == 'w') {
10640 second.matchWins++;
10645 if (first.twoMachinesColor[0] == 'b') {
10648 second.matchWins++;
10651 case GameUnfinished:
10657 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10658 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10659 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10660 ReserveGame(nextGame, resChar); // sets nextGame
10661 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10662 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10663 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10665 if (nextGame <= appData.matchGames && !abortMatch) {
10666 gameMode = nextGameMode;
10667 matchGame = nextGame; // this will be overruled in tourney mode!
10668 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10669 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10670 endingGame = 0; /* [HGM] crash */
10673 gameMode = nextGameMode;
10674 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10675 first.tidy, second.tidy,
10676 first.matchWins, second.matchWins,
10677 appData.matchGames - (first.matchWins + second.matchWins));
10678 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10679 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10680 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10681 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10682 first.twoMachinesColor = "black\n";
10683 second.twoMachinesColor = "white\n";
10685 first.twoMachinesColor = "white\n";
10686 second.twoMachinesColor = "black\n";
10690 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10691 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10693 gameMode = nextGameMode;
10695 endingGame = 0; /* [HGM] crash */
10696 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10697 if(matchMode == TRUE) { // match through command line: exit with or without popup
10699 ToNrEvent(forwardMostMove);
10700 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10702 } else DisplayFatalError(buf, 0, 0);
10703 } else { // match through menu; just stop, with or without popup
10704 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10707 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10708 } else DisplayNote(buf);
10710 if(ranking) free(ranking);
10714 /* Assumes program was just initialized (initString sent).
10715 Leaves program in force mode. */
10717 FeedMovesToProgram (ChessProgramState *cps, int upto)
10721 if (appData.debugMode)
10722 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10723 startedFromSetupPosition ? "position and " : "",
10724 backwardMostMove, upto, cps->which);
10725 if(currentlyInitializedVariant != gameInfo.variant) {
10727 // [HGM] variantswitch: make engine aware of new variant
10728 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10729 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10730 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10731 SendToProgram(buf, cps);
10732 currentlyInitializedVariant = gameInfo.variant;
10734 SendToProgram("force\n", cps);
10735 if (startedFromSetupPosition) {
10736 SendBoard(cps, backwardMostMove);
10737 if (appData.debugMode) {
10738 fprintf(debugFP, "feedMoves\n");
10741 for (i = backwardMostMove; i < upto; i++) {
10742 SendMoveToProgram(i, cps);
10748 ResurrectChessProgram ()
10750 /* The chess program may have exited.
10751 If so, restart it and feed it all the moves made so far. */
10752 static int doInit = 0;
10754 if (appData.noChessProgram) return 1;
10756 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10757 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10758 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10759 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10761 if (first.pr != NoProc) return 1;
10762 StartChessProgram(&first);
10764 InitChessProgram(&first, FALSE);
10765 FeedMovesToProgram(&first, currentMove);
10767 if (!first.sendTime) {
10768 /* can't tell gnuchess what its clock should read,
10769 so we bow to its notion. */
10771 timeRemaining[0][currentMove] = whiteTimeRemaining;
10772 timeRemaining[1][currentMove] = blackTimeRemaining;
10775 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10776 appData.icsEngineAnalyze) && first.analysisSupport) {
10777 SendToProgram("analyze\n", &first);
10778 first.analyzing = TRUE;
10784 * Button procedures
10787 Reset (int redraw, int init)
10791 if (appData.debugMode) {
10792 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10793 redraw, init, gameMode);
10795 CleanupTail(); // [HGM] vari: delete any stored variations
10796 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10797 pausing = pauseExamInvalid = FALSE;
10798 startedFromSetupPosition = blackPlaysFirst = FALSE;
10800 whiteFlag = blackFlag = FALSE;
10801 userOfferedDraw = FALSE;
10802 hintRequested = bookRequested = FALSE;
10803 first.maybeThinking = FALSE;
10804 second.maybeThinking = FALSE;
10805 first.bookSuspend = FALSE; // [HGM] book
10806 second.bookSuspend = FALSE;
10807 thinkOutput[0] = NULLCHAR;
10808 lastHint[0] = NULLCHAR;
10809 ClearGameInfo(&gameInfo);
10810 gameInfo.variant = StringToVariant(appData.variant);
10811 ics_user_moved = ics_clock_paused = FALSE;
10812 ics_getting_history = H_FALSE;
10814 white_holding[0] = black_holding[0] = NULLCHAR;
10815 ClearProgramStats();
10816 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10820 flipView = appData.flipView;
10821 ClearPremoveHighlights();
10822 gotPremove = FALSE;
10823 alarmSounded = FALSE;
10825 GameEnds(EndOfFile, NULL, GE_PLAYER);
10826 if(appData.serverMovesName != NULL) {
10827 /* [HGM] prepare to make moves file for broadcasting */
10828 clock_t t = clock();
10829 if(serverMoves != NULL) fclose(serverMoves);
10830 serverMoves = fopen(appData.serverMovesName, "r");
10831 if(serverMoves != NULL) {
10832 fclose(serverMoves);
10833 /* delay 15 sec before overwriting, so all clients can see end */
10834 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10836 serverMoves = fopen(appData.serverMovesName, "w");
10840 gameMode = BeginningOfGame;
10842 if(appData.icsActive) gameInfo.variant = VariantNormal;
10843 currentMove = forwardMostMove = backwardMostMove = 0;
10844 MarkTargetSquares(1);
10845 InitPosition(redraw);
10846 for (i = 0; i < MAX_MOVES; i++) {
10847 if (commentList[i] != NULL) {
10848 free(commentList[i]);
10849 commentList[i] = NULL;
10853 timeRemaining[0][0] = whiteTimeRemaining;
10854 timeRemaining[1][0] = blackTimeRemaining;
10856 if (first.pr == NoProc) {
10857 StartChessProgram(&first);
10860 InitChessProgram(&first, startedFromSetupPosition);
10863 DisplayMessage("", "");
10864 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10865 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10866 ClearMap(); // [HGM] exclude: invalidate map
10870 AutoPlayGameLoop ()
10873 if (!AutoPlayOneMove())
10875 if (matchMode || appData.timeDelay == 0)
10877 if (appData.timeDelay < 0)
10879 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10888 int fromX, fromY, toX, toY;
10890 if (appData.debugMode) {
10891 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10894 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10897 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10898 pvInfoList[currentMove].depth = programStats.depth;
10899 pvInfoList[currentMove].score = programStats.score;
10900 pvInfoList[currentMove].time = 0;
10901 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10904 if (currentMove >= forwardMostMove) {
10905 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10906 // gameMode = EndOfGame;
10907 // ModeHighlight();
10909 /* [AS] Clear current move marker at the end of a game */
10910 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10915 toX = moveList[currentMove][2] - AAA;
10916 toY = moveList[currentMove][3] - ONE;
10918 if (moveList[currentMove][1] == '@') {
10919 if (appData.highlightLastMove) {
10920 SetHighlights(-1, -1, toX, toY);
10923 fromX = moveList[currentMove][0] - AAA;
10924 fromY = moveList[currentMove][1] - ONE;
10926 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10928 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10930 if (appData.highlightLastMove) {
10931 SetHighlights(fromX, fromY, toX, toY);
10934 DisplayMove(currentMove);
10935 SendMoveToProgram(currentMove++, &first);
10936 DisplayBothClocks();
10937 DrawPosition(FALSE, boards[currentMove]);
10938 // [HGM] PV info: always display, routine tests if empty
10939 DisplayComment(currentMove - 1, commentList[currentMove]);
10945 LoadGameOneMove (ChessMove readAhead)
10947 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10948 char promoChar = NULLCHAR;
10949 ChessMove moveType;
10950 char move[MSG_SIZ];
10953 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10954 gameMode != AnalyzeMode && gameMode != Training) {
10959 yyboardindex = forwardMostMove;
10960 if (readAhead != EndOfFile) {
10961 moveType = readAhead;
10963 if (gameFileFP == NULL)
10965 moveType = (ChessMove) Myylex();
10969 switch (moveType) {
10971 if (appData.debugMode)
10972 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10975 /* append the comment but don't display it */
10976 AppendComment(currentMove, p, FALSE);
10979 case WhiteCapturesEnPassant:
10980 case BlackCapturesEnPassant:
10981 case WhitePromotion:
10982 case BlackPromotion:
10983 case WhiteNonPromotion:
10984 case BlackNonPromotion:
10986 case WhiteKingSideCastle:
10987 case WhiteQueenSideCastle:
10988 case BlackKingSideCastle:
10989 case BlackQueenSideCastle:
10990 case WhiteKingSideCastleWild:
10991 case WhiteQueenSideCastleWild:
10992 case BlackKingSideCastleWild:
10993 case BlackQueenSideCastleWild:
10995 case WhiteHSideCastleFR:
10996 case WhiteASideCastleFR:
10997 case BlackHSideCastleFR:
10998 case BlackASideCastleFR:
11000 if (appData.debugMode)
11001 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11002 fromX = currentMoveString[0] - AAA;
11003 fromY = currentMoveString[1] - ONE;
11004 toX = currentMoveString[2] - AAA;
11005 toY = currentMoveString[3] - ONE;
11006 promoChar = currentMoveString[4];
11011 if (appData.debugMode)
11012 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11013 fromX = moveType == WhiteDrop ?
11014 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11015 (int) CharToPiece(ToLower(currentMoveString[0]));
11017 toX = currentMoveString[2] - AAA;
11018 toY = currentMoveString[3] - ONE;
11024 case GameUnfinished:
11025 if (appData.debugMode)
11026 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11027 p = strchr(yy_text, '{');
11028 if (p == NULL) p = strchr(yy_text, '(');
11031 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11033 q = strchr(p, *p == '{' ? '}' : ')');
11034 if (q != NULL) *q = NULLCHAR;
11037 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11038 GameEnds(moveType, p, GE_FILE);
11040 if (cmailMsgLoaded) {
11042 flipView = WhiteOnMove(currentMove);
11043 if (moveType == GameUnfinished) flipView = !flipView;
11044 if (appData.debugMode)
11045 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11050 if (appData.debugMode)
11051 fprintf(debugFP, "Parser hit end of file\n");
11052 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11058 if (WhiteOnMove(currentMove)) {
11059 GameEnds(BlackWins, "Black mates", GE_FILE);
11061 GameEnds(WhiteWins, "White mates", GE_FILE);
11065 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11071 case MoveNumberOne:
11072 if (lastLoadGameStart == GNUChessGame) {
11073 /* GNUChessGames have numbers, but they aren't move numbers */
11074 if (appData.debugMode)
11075 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11076 yy_text, (int) moveType);
11077 return LoadGameOneMove(EndOfFile); /* tail recursion */
11079 /* else fall thru */
11084 /* Reached start of next game in file */
11085 if (appData.debugMode)
11086 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11087 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11093 if (WhiteOnMove(currentMove)) {
11094 GameEnds(BlackWins, "Black mates", GE_FILE);
11096 GameEnds(WhiteWins, "White mates", GE_FILE);
11100 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11106 case PositionDiagram: /* should not happen; ignore */
11107 case ElapsedTime: /* ignore */
11108 case NAG: /* ignore */
11109 if (appData.debugMode)
11110 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11111 yy_text, (int) moveType);
11112 return LoadGameOneMove(EndOfFile); /* tail recursion */
11115 if (appData.testLegality) {
11116 if (appData.debugMode)
11117 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11118 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11119 (forwardMostMove / 2) + 1,
11120 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11121 DisplayError(move, 0);
11124 if (appData.debugMode)
11125 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11126 yy_text, currentMoveString);
11127 fromX = currentMoveString[0] - AAA;
11128 fromY = currentMoveString[1] - ONE;
11129 toX = currentMoveString[2] - AAA;
11130 toY = currentMoveString[3] - ONE;
11131 promoChar = currentMoveString[4];
11135 case AmbiguousMove:
11136 if (appData.debugMode)
11137 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11138 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11139 (forwardMostMove / 2) + 1,
11140 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11141 DisplayError(move, 0);
11146 case ImpossibleMove:
11147 if (appData.debugMode)
11148 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11149 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11150 (forwardMostMove / 2) + 1,
11151 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11152 DisplayError(move, 0);
11158 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11159 DrawPosition(FALSE, boards[currentMove]);
11160 DisplayBothClocks();
11161 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11162 DisplayComment(currentMove - 1, commentList[currentMove]);
11164 (void) StopLoadGameTimer();
11166 cmailOldMove = forwardMostMove;
11169 /* currentMoveString is set as a side-effect of yylex */
11171 thinkOutput[0] = NULLCHAR;
11172 MakeMove(fromX, fromY, toX, toY, promoChar);
11173 currentMove = forwardMostMove;
11178 /* Load the nth game from the given file */
11180 LoadGameFromFile (char *filename, int n, char *title, int useList)
11185 if (strcmp(filename, "-") == 0) {
11189 f = fopen(filename, "rb");
11191 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11192 DisplayError(buf, errno);
11196 if (fseek(f, 0, 0) == -1) {
11197 /* f is not seekable; probably a pipe */
11200 if (useList && n == 0) {
11201 int error = GameListBuild(f);
11203 DisplayError(_("Cannot build game list"), error);
11204 } else if (!ListEmpty(&gameList) &&
11205 ((ListGame *) gameList.tailPred)->number > 1) {
11206 GameListPopUp(f, title);
11213 return LoadGame(f, n, title, FALSE);
11218 MakeRegisteredMove ()
11220 int fromX, fromY, toX, toY;
11222 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11223 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11226 if (appData.debugMode)
11227 fprintf(debugFP, "Restoring %s for game %d\n",
11228 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11230 thinkOutput[0] = NULLCHAR;
11231 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11232 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11233 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11234 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11235 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11236 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11237 MakeMove(fromX, fromY, toX, toY, promoChar);
11238 ShowMove(fromX, fromY, toX, toY);
11240 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11247 if (WhiteOnMove(currentMove)) {
11248 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11250 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11255 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11262 if (WhiteOnMove(currentMove)) {
11263 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11265 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11270 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11281 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11283 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11287 if (gameNumber > nCmailGames) {
11288 DisplayError(_("No more games in this message"), 0);
11291 if (f == lastLoadGameFP) {
11292 int offset = gameNumber - lastLoadGameNumber;
11294 cmailMsg[0] = NULLCHAR;
11295 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11296 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11297 nCmailMovesRegistered--;
11299 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11300 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11301 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11304 if (! RegisterMove()) return FALSE;
11308 retVal = LoadGame(f, gameNumber, title, useList);
11310 /* Make move registered during previous look at this game, if any */
11311 MakeRegisteredMove();
11313 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11314 commentList[currentMove]
11315 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11316 DisplayComment(currentMove - 1, commentList[currentMove]);
11322 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11324 ReloadGame (int offset)
11326 int gameNumber = lastLoadGameNumber + offset;
11327 if (lastLoadGameFP == NULL) {
11328 DisplayError(_("No game has been loaded yet"), 0);
11331 if (gameNumber <= 0) {
11332 DisplayError(_("Can't back up any further"), 0);
11335 if (cmailMsgLoaded) {
11336 return CmailLoadGame(lastLoadGameFP, gameNumber,
11337 lastLoadGameTitle, lastLoadGameUseList);
11339 return LoadGame(lastLoadGameFP, gameNumber,
11340 lastLoadGameTitle, lastLoadGameUseList);
11344 int keys[EmptySquare+1];
11347 PositionMatches (Board b1, Board b2)
11350 switch(appData.searchMode) {
11351 case 1: return CompareWithRights(b1, b2);
11353 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11354 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11358 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11359 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11360 sum += keys[b1[r][f]] - keys[b2[r][f]];
11364 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11365 sum += keys[b1[r][f]] - keys[b2[r][f]];
11377 int pieceList[256], quickBoard[256];
11378 ChessSquare pieceType[256] = { EmptySquare };
11379 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11380 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11381 int soughtTotal, turn;
11382 Boolean epOK, flipSearch;
11385 unsigned char piece, to;
11388 #define DSIZE (250000)
11390 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11391 Move *moveDatabase = initialSpace;
11392 unsigned int movePtr, dataSize = DSIZE;
11395 MakePieceList (Board board, int *counts)
11397 int r, f, n=Q_PROMO, total=0;
11398 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11399 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11400 int sq = f + (r<<4);
11401 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11402 quickBoard[sq] = ++n;
11404 pieceType[n] = board[r][f];
11405 counts[board[r][f]]++;
11406 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11407 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11411 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11416 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11418 int sq = fromX + (fromY<<4);
11419 int piece = quickBoard[sq];
11420 quickBoard[sq] = 0;
11421 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11422 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11423 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11424 moveDatabase[movePtr++].piece = Q_WCASTL;
11425 quickBoard[sq] = piece;
11426 piece = quickBoard[from]; quickBoard[from] = 0;
11427 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11429 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11430 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11431 moveDatabase[movePtr++].piece = Q_BCASTL;
11432 quickBoard[sq] = piece;
11433 piece = quickBoard[from]; quickBoard[from] = 0;
11434 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11436 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11437 quickBoard[(fromY<<4)+toX] = 0;
11438 moveDatabase[movePtr].piece = Q_EP;
11439 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11440 moveDatabase[movePtr].to = sq;
11442 if(promoPiece != pieceType[piece]) {
11443 moveDatabase[movePtr++].piece = Q_PROMO;
11444 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11446 moveDatabase[movePtr].piece = piece;
11447 quickBoard[sq] = piece;
11452 PackGame (Board board)
11454 Move *newSpace = NULL;
11455 moveDatabase[movePtr].piece = 0; // terminate previous game
11456 if(movePtr > dataSize) {
11457 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11458 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11459 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11462 Move *p = moveDatabase, *q = newSpace;
11463 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11464 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11465 moveDatabase = newSpace;
11466 } else { // calloc failed, we must be out of memory. Too bad...
11467 dataSize = 0; // prevent calloc events for all subsequent games
11468 return 0; // and signal this one isn't cached
11472 MakePieceList(board, counts);
11477 QuickCompare (Board board, int *minCounts, int *maxCounts)
11478 { // compare according to search mode
11480 switch(appData.searchMode)
11482 case 1: // exact position match
11483 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11484 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11485 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11488 case 2: // can have extra material on empty squares
11489 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11490 if(board[r][f] == EmptySquare) continue;
11491 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11494 case 3: // material with exact Pawn structure
11495 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11496 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11497 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11498 } // fall through to material comparison
11499 case 4: // exact material
11500 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11502 case 6: // material range with given imbalance
11503 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11504 // fall through to range comparison
11505 case 5: // material range
11506 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11512 QuickScan (Board board, Move *move)
11513 { // reconstruct game,and compare all positions in it
11514 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11516 int piece = move->piece;
11517 int to = move->to, from = pieceList[piece];
11518 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11519 if(!piece) return -1;
11520 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11521 piece = (++move)->piece;
11522 from = pieceList[piece];
11523 counts[pieceType[piece]]--;
11524 pieceType[piece] = (ChessSquare) move->to;
11525 counts[move->to]++;
11526 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11527 counts[pieceType[quickBoard[to]]]--;
11528 quickBoard[to] = 0; total--;
11531 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11532 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11533 from = pieceList[piece]; // so this must be King
11534 quickBoard[from] = 0;
11535 quickBoard[to] = piece;
11536 pieceList[piece] = to;
11541 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11542 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11543 quickBoard[from] = 0;
11544 quickBoard[to] = piece;
11545 pieceList[piece] = to;
11547 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11548 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11549 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11550 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11552 static int lastCounts[EmptySquare+1];
11554 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11555 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11556 } else stretch = 0;
11557 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11566 flipSearch = FALSE;
11567 CopyBoard(soughtBoard, boards[currentMove]);
11568 soughtTotal = MakePieceList(soughtBoard, maxSought);
11569 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11570 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11571 CopyBoard(reverseBoard, boards[currentMove]);
11572 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11573 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11574 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11575 reverseBoard[r][f] = piece;
11577 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11578 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11579 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11580 || (boards[currentMove][CASTLING][2] == NoRights ||
11581 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11582 && (boards[currentMove][CASTLING][5] == NoRights ||
11583 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11586 CopyBoard(flipBoard, soughtBoard);
11587 CopyBoard(rotateBoard, reverseBoard);
11588 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11589 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11590 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11593 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11594 if(appData.searchMode >= 5) {
11595 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11596 MakePieceList(soughtBoard, minSought);
11597 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11599 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11600 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11603 GameInfo dummyInfo;
11606 GameContainsPosition (FILE *f, ListGame *lg)
11608 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11609 int fromX, fromY, toX, toY;
11611 static int initDone=FALSE;
11613 // weed out games based on numerical tag comparison
11614 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11615 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11616 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11617 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11619 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11622 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11623 else CopyBoard(boards[scratch], initialPosition); // default start position
11626 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11627 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11630 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11631 fseek(f, lg->offset, 0);
11634 yyboardindex = scratch;
11635 quickFlag = plyNr+1;
11640 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11646 if(plyNr) return -1; // after we have seen moves, this is for new game
11649 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11650 case ImpossibleMove:
11651 case WhiteWins: // game ends here with these four
11654 case GameUnfinished:
11658 if(appData.testLegality) return -1;
11659 case WhiteCapturesEnPassant:
11660 case BlackCapturesEnPassant:
11661 case WhitePromotion:
11662 case BlackPromotion:
11663 case WhiteNonPromotion:
11664 case BlackNonPromotion:
11666 case WhiteKingSideCastle:
11667 case WhiteQueenSideCastle:
11668 case BlackKingSideCastle:
11669 case BlackQueenSideCastle:
11670 case WhiteKingSideCastleWild:
11671 case WhiteQueenSideCastleWild:
11672 case BlackKingSideCastleWild:
11673 case BlackQueenSideCastleWild:
11674 case WhiteHSideCastleFR:
11675 case WhiteASideCastleFR:
11676 case BlackHSideCastleFR:
11677 case BlackASideCastleFR:
11678 fromX = currentMoveString[0] - AAA;
11679 fromY = currentMoveString[1] - ONE;
11680 toX = currentMoveString[2] - AAA;
11681 toY = currentMoveString[3] - ONE;
11682 promoChar = currentMoveString[4];
11686 fromX = next == WhiteDrop ?
11687 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11688 (int) CharToPiece(ToLower(currentMoveString[0]));
11690 toX = currentMoveString[2] - AAA;
11691 toY = currentMoveString[3] - ONE;
11695 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11697 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11698 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11699 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11700 if(appData.findMirror) {
11701 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11702 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11707 /* Load the nth game from open file f */
11709 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11713 int gn = gameNumber;
11714 ListGame *lg = NULL;
11715 int numPGNTags = 0;
11717 GameMode oldGameMode;
11718 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11720 if (appData.debugMode)
11721 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11723 if (gameMode == Training )
11724 SetTrainingModeOff();
11726 oldGameMode = gameMode;
11727 if (gameMode != BeginningOfGame) {
11728 Reset(FALSE, TRUE);
11732 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11733 fclose(lastLoadGameFP);
11737 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11740 fseek(f, lg->offset, 0);
11741 GameListHighlight(gameNumber);
11742 pos = lg->position;
11746 DisplayError(_("Game number out of range"), 0);
11751 if (fseek(f, 0, 0) == -1) {
11752 if (f == lastLoadGameFP ?
11753 gameNumber == lastLoadGameNumber + 1 :
11757 DisplayError(_("Can't seek on game file"), 0);
11762 lastLoadGameFP = f;
11763 lastLoadGameNumber = gameNumber;
11764 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11765 lastLoadGameUseList = useList;
11769 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11770 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11771 lg->gameInfo.black);
11773 } else if (*title != NULLCHAR) {
11774 if (gameNumber > 1) {
11775 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11778 DisplayTitle(title);
11782 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11783 gameMode = PlayFromGameFile;
11787 currentMove = forwardMostMove = backwardMostMove = 0;
11788 CopyBoard(boards[0], initialPosition);
11792 * Skip the first gn-1 games in the file.
11793 * Also skip over anything that precedes an identifiable
11794 * start of game marker, to avoid being confused by
11795 * garbage at the start of the file. Currently
11796 * recognized start of game markers are the move number "1",
11797 * the pattern "gnuchess .* game", the pattern
11798 * "^[#;%] [^ ]* game file", and a PGN tag block.
11799 * A game that starts with one of the latter two patterns
11800 * will also have a move number 1, possibly
11801 * following a position diagram.
11802 * 5-4-02: Let's try being more lenient and allowing a game to
11803 * start with an unnumbered move. Does that break anything?
11805 cm = lastLoadGameStart = EndOfFile;
11807 yyboardindex = forwardMostMove;
11808 cm = (ChessMove) Myylex();
11811 if (cmailMsgLoaded) {
11812 nCmailGames = CMAIL_MAX_GAMES - gn;
11815 DisplayError(_("Game not found in file"), 0);
11822 lastLoadGameStart = cm;
11825 case MoveNumberOne:
11826 switch (lastLoadGameStart) {
11831 case MoveNumberOne:
11833 gn--; /* count this game */
11834 lastLoadGameStart = cm;
11843 switch (lastLoadGameStart) {
11846 case MoveNumberOne:
11848 gn--; /* count this game */
11849 lastLoadGameStart = cm;
11852 lastLoadGameStart = cm; /* game counted already */
11860 yyboardindex = forwardMostMove;
11861 cm = (ChessMove) Myylex();
11862 } while (cm == PGNTag || cm == Comment);
11869 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11870 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11871 != CMAIL_OLD_RESULT) {
11873 cmailResult[ CMAIL_MAX_GAMES
11874 - gn - 1] = CMAIL_OLD_RESULT;
11880 /* Only a NormalMove can be at the start of a game
11881 * without a position diagram. */
11882 if (lastLoadGameStart == EndOfFile ) {
11884 lastLoadGameStart = MoveNumberOne;
11893 if (appData.debugMode)
11894 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11896 if (cm == XBoardGame) {
11897 /* Skip any header junk before position diagram and/or move 1 */
11899 yyboardindex = forwardMostMove;
11900 cm = (ChessMove) Myylex();
11902 if (cm == EndOfFile ||
11903 cm == GNUChessGame || cm == XBoardGame) {
11904 /* Empty game; pretend end-of-file and handle later */
11909 if (cm == MoveNumberOne || cm == PositionDiagram ||
11910 cm == PGNTag || cm == Comment)
11913 } else if (cm == GNUChessGame) {
11914 if (gameInfo.event != NULL) {
11915 free(gameInfo.event);
11917 gameInfo.event = StrSave(yy_text);
11920 startedFromSetupPosition = FALSE;
11921 while (cm == PGNTag) {
11922 if (appData.debugMode)
11923 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11924 err = ParsePGNTag(yy_text, &gameInfo);
11925 if (!err) numPGNTags++;
11927 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11928 if(gameInfo.variant != oldVariant) {
11929 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11930 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11931 InitPosition(TRUE);
11932 oldVariant = gameInfo.variant;
11933 if (appData.debugMode)
11934 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11938 if (gameInfo.fen != NULL) {
11939 Board initial_position;
11940 startedFromSetupPosition = TRUE;
11941 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11943 DisplayError(_("Bad FEN position in file"), 0);
11946 CopyBoard(boards[0], initial_position);
11947 if (blackPlaysFirst) {
11948 currentMove = forwardMostMove = backwardMostMove = 1;
11949 CopyBoard(boards[1], initial_position);
11950 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11951 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11952 timeRemaining[0][1] = whiteTimeRemaining;
11953 timeRemaining[1][1] = blackTimeRemaining;
11954 if (commentList[0] != NULL) {
11955 commentList[1] = commentList[0];
11956 commentList[0] = NULL;
11959 currentMove = forwardMostMove = backwardMostMove = 0;
11961 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11963 initialRulePlies = FENrulePlies;
11964 for( i=0; i< nrCastlingRights; i++ )
11965 initialRights[i] = initial_position[CASTLING][i];
11967 yyboardindex = forwardMostMove;
11968 free(gameInfo.fen);
11969 gameInfo.fen = NULL;
11972 yyboardindex = forwardMostMove;
11973 cm = (ChessMove) Myylex();
11975 /* Handle comments interspersed among the tags */
11976 while (cm == Comment) {
11978 if (appData.debugMode)
11979 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11981 AppendComment(currentMove, p, FALSE);
11982 yyboardindex = forwardMostMove;
11983 cm = (ChessMove) Myylex();
11987 /* don't rely on existence of Event tag since if game was
11988 * pasted from clipboard the Event tag may not exist
11990 if (numPGNTags > 0){
11992 if (gameInfo.variant == VariantNormal) {
11993 VariantClass v = StringToVariant(gameInfo.event);
11994 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11995 if(v < VariantShogi) gameInfo.variant = v;
11998 if( appData.autoDisplayTags ) {
11999 tags = PGNTags(&gameInfo);
12000 TagsPopUp(tags, CmailMsg());
12005 /* Make something up, but don't display it now */
12010 if (cm == PositionDiagram) {
12013 Board initial_position;
12015 if (appData.debugMode)
12016 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12018 if (!startedFromSetupPosition) {
12020 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12021 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12032 initial_position[i][j++] = CharToPiece(*p);
12035 while (*p == ' ' || *p == '\t' ||
12036 *p == '\n' || *p == '\r') p++;
12038 if (strncmp(p, "black", strlen("black"))==0)
12039 blackPlaysFirst = TRUE;
12041 blackPlaysFirst = FALSE;
12042 startedFromSetupPosition = TRUE;
12044 CopyBoard(boards[0], initial_position);
12045 if (blackPlaysFirst) {
12046 currentMove = forwardMostMove = backwardMostMove = 1;
12047 CopyBoard(boards[1], initial_position);
12048 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12049 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12050 timeRemaining[0][1] = whiteTimeRemaining;
12051 timeRemaining[1][1] = blackTimeRemaining;
12052 if (commentList[0] != NULL) {
12053 commentList[1] = commentList[0];
12054 commentList[0] = NULL;
12057 currentMove = forwardMostMove = backwardMostMove = 0;
12060 yyboardindex = forwardMostMove;
12061 cm = (ChessMove) Myylex();
12064 if (first.pr == NoProc) {
12065 StartChessProgram(&first);
12067 InitChessProgram(&first, FALSE);
12068 SendToProgram("force\n", &first);
12069 if (startedFromSetupPosition) {
12070 SendBoard(&first, forwardMostMove);
12071 if (appData.debugMode) {
12072 fprintf(debugFP, "Load Game\n");
12074 DisplayBothClocks();
12077 /* [HGM] server: flag to write setup moves in broadcast file as one */
12078 loadFlag = appData.suppressLoadMoves;
12080 while (cm == Comment) {
12082 if (appData.debugMode)
12083 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12085 AppendComment(currentMove, p, FALSE);
12086 yyboardindex = forwardMostMove;
12087 cm = (ChessMove) Myylex();
12090 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12091 cm == WhiteWins || cm == BlackWins ||
12092 cm == GameIsDrawn || cm == GameUnfinished) {
12093 DisplayMessage("", _("No moves in game"));
12094 if (cmailMsgLoaded) {
12095 if (appData.debugMode)
12096 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12100 DrawPosition(FALSE, boards[currentMove]);
12101 DisplayBothClocks();
12102 gameMode = EditGame;
12109 // [HGM] PV info: routine tests if comment empty
12110 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12111 DisplayComment(currentMove - 1, commentList[currentMove]);
12113 if (!matchMode && appData.timeDelay != 0)
12114 DrawPosition(FALSE, boards[currentMove]);
12116 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12117 programStats.ok_to_send = 1;
12120 /* if the first token after the PGN tags is a move
12121 * and not move number 1, retrieve it from the parser
12123 if (cm != MoveNumberOne)
12124 LoadGameOneMove(cm);
12126 /* load the remaining moves from the file */
12127 while (LoadGameOneMove(EndOfFile)) {
12128 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12129 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12132 /* rewind to the start of the game */
12133 currentMove = backwardMostMove;
12135 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12137 if (oldGameMode == AnalyzeFile ||
12138 oldGameMode == AnalyzeMode) {
12139 AnalyzeFileEvent();
12142 if (!matchMode && pos >= 0) {
12143 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12145 if (matchMode || appData.timeDelay == 0) {
12147 } else if (appData.timeDelay > 0) {
12148 AutoPlayGameLoop();
12151 if (appData.debugMode)
12152 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12154 loadFlag = 0; /* [HGM] true game starts */
12158 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12160 ReloadPosition (int offset)
12162 int positionNumber = lastLoadPositionNumber + offset;
12163 if (lastLoadPositionFP == NULL) {
12164 DisplayError(_("No position has been loaded yet"), 0);
12167 if (positionNumber <= 0) {
12168 DisplayError(_("Can't back up any further"), 0);
12171 return LoadPosition(lastLoadPositionFP, positionNumber,
12172 lastLoadPositionTitle);
12175 /* Load the nth position from the given file */
12177 LoadPositionFromFile (char *filename, int n, char *title)
12182 if (strcmp(filename, "-") == 0) {
12183 return LoadPosition(stdin, n, "stdin");
12185 f = fopen(filename, "rb");
12187 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12188 DisplayError(buf, errno);
12191 return LoadPosition(f, n, title);
12196 /* Load the nth position from the given open file, and close it */
12198 LoadPosition (FILE *f, int positionNumber, char *title)
12200 char *p, line[MSG_SIZ];
12201 Board initial_position;
12202 int i, j, fenMode, pn;
12204 if (gameMode == Training )
12205 SetTrainingModeOff();
12207 if (gameMode != BeginningOfGame) {
12208 Reset(FALSE, TRUE);
12210 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12211 fclose(lastLoadPositionFP);
12213 if (positionNumber == 0) positionNumber = 1;
12214 lastLoadPositionFP = f;
12215 lastLoadPositionNumber = positionNumber;
12216 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12217 if (first.pr == NoProc && !appData.noChessProgram) {
12218 StartChessProgram(&first);
12219 InitChessProgram(&first, FALSE);
12221 pn = positionNumber;
12222 if (positionNumber < 0) {
12223 /* Negative position number means to seek to that byte offset */
12224 if (fseek(f, -positionNumber, 0) == -1) {
12225 DisplayError(_("Can't seek on position file"), 0);
12230 if (fseek(f, 0, 0) == -1) {
12231 if (f == lastLoadPositionFP ?
12232 positionNumber == lastLoadPositionNumber + 1 :
12233 positionNumber == 1) {
12236 DisplayError(_("Can't seek on position file"), 0);
12241 /* See if this file is FEN or old-style xboard */
12242 if (fgets(line, MSG_SIZ, f) == NULL) {
12243 DisplayError(_("Position not found in file"), 0);
12246 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12247 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12250 if (fenMode || line[0] == '#') pn--;
12252 /* skip positions before number pn */
12253 if (fgets(line, MSG_SIZ, f) == NULL) {
12255 DisplayError(_("Position not found in file"), 0);
12258 if (fenMode || line[0] == '#') pn--;
12263 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12264 DisplayError(_("Bad FEN position in file"), 0);
12268 (void) fgets(line, MSG_SIZ, f);
12269 (void) fgets(line, MSG_SIZ, f);
12271 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12272 (void) fgets(line, MSG_SIZ, f);
12273 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12276 initial_position[i][j++] = CharToPiece(*p);
12280 blackPlaysFirst = FALSE;
12282 (void) fgets(line, MSG_SIZ, f);
12283 if (strncmp(line, "black", strlen("black"))==0)
12284 blackPlaysFirst = TRUE;
12287 startedFromSetupPosition = TRUE;
12289 CopyBoard(boards[0], initial_position);
12290 if (blackPlaysFirst) {
12291 currentMove = forwardMostMove = backwardMostMove = 1;
12292 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12293 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12294 CopyBoard(boards[1], initial_position);
12295 DisplayMessage("", _("Black to play"));
12297 currentMove = forwardMostMove = backwardMostMove = 0;
12298 DisplayMessage("", _("White to play"));
12300 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12301 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12302 SendToProgram("force\n", &first);
12303 SendBoard(&first, forwardMostMove);
12305 if (appData.debugMode) {
12307 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12308 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12309 fprintf(debugFP, "Load Position\n");
12312 if (positionNumber > 1) {
12313 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12314 DisplayTitle(line);
12316 DisplayTitle(title);
12318 gameMode = EditGame;
12321 timeRemaining[0][1] = whiteTimeRemaining;
12322 timeRemaining[1][1] = blackTimeRemaining;
12323 DrawPosition(FALSE, boards[currentMove]);
12330 CopyPlayerNameIntoFileName (char **dest, char *src)
12332 while (*src != NULLCHAR && *src != ',') {
12337 *(*dest)++ = *src++;
12343 DefaultFileName (char *ext)
12345 static char def[MSG_SIZ];
12348 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12350 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12352 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12354 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12361 /* Save the current game to the given file */
12363 SaveGameToFile (char *filename, int append)
12367 int result, i, t,tot=0;
12369 if (strcmp(filename, "-") == 0) {
12370 return SaveGame(stdout, 0, NULL);
12372 for(i=0; i<10; i++) { // upto 10 tries
12373 f = fopen(filename, append ? "a" : "w");
12374 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12375 if(f || errno != 13) break;
12376 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12380 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12381 DisplayError(buf, errno);
12384 safeStrCpy(buf, lastMsg, MSG_SIZ);
12385 DisplayMessage(_("Waiting for access to save file"), "");
12386 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12387 DisplayMessage(_("Saving game"), "");
12388 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12389 result = SaveGame(f, 0, NULL);
12390 DisplayMessage(buf, "");
12397 SavePart (char *str)
12399 static char buf[MSG_SIZ];
12402 p = strchr(str, ' ');
12403 if (p == NULL) return str;
12404 strncpy(buf, str, p - str);
12405 buf[p - str] = NULLCHAR;
12409 #define PGN_MAX_LINE 75
12411 #define PGN_SIDE_WHITE 0
12412 #define PGN_SIDE_BLACK 1
12415 FindFirstMoveOutOfBook (int side)
12419 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12420 int index = backwardMostMove;
12421 int has_book_hit = 0;
12423 if( (index % 2) != side ) {
12427 while( index < forwardMostMove ) {
12428 /* Check to see if engine is in book */
12429 int depth = pvInfoList[index].depth;
12430 int score = pvInfoList[index].score;
12436 else if( score == 0 && depth == 63 ) {
12437 in_book = 1; /* Zappa */
12439 else if( score == 2 && depth == 99 ) {
12440 in_book = 1; /* Abrok */
12443 has_book_hit += in_book;
12459 GetOutOfBookInfo (char * buf)
12463 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12465 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12466 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12470 if( oob[0] >= 0 || oob[1] >= 0 ) {
12471 for( i=0; i<2; i++ ) {
12475 if( i > 0 && oob[0] >= 0 ) {
12476 strcat( buf, " " );
12479 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12480 sprintf( buf+strlen(buf), "%s%.2f",
12481 pvInfoList[idx].score >= 0 ? "+" : "",
12482 pvInfoList[idx].score / 100.0 );
12488 /* Save game in PGN style and close the file */
12490 SaveGamePGN (FILE *f)
12492 int i, offset, linelen, newblock;
12496 int movelen, numlen, blank;
12497 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12499 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12501 tm = time((time_t *) NULL);
12503 PrintPGNTags(f, &gameInfo);
12505 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12507 if (backwardMostMove > 0 || startedFromSetupPosition) {
12508 char *fen = PositionToFEN(backwardMostMove, NULL);
12509 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12510 fprintf(f, "\n{--------------\n");
12511 PrintPosition(f, backwardMostMove);
12512 fprintf(f, "--------------}\n");
12516 /* [AS] Out of book annotation */
12517 if( appData.saveOutOfBookInfo ) {
12520 GetOutOfBookInfo( buf );
12522 if( buf[0] != '\0' ) {
12523 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12530 i = backwardMostMove;
12534 while (i < forwardMostMove) {
12535 /* Print comments preceding this move */
12536 if (commentList[i] != NULL) {
12537 if (linelen > 0) fprintf(f, "\n");
12538 fprintf(f, "%s", commentList[i]);
12543 /* Format move number */
12545 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12548 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12550 numtext[0] = NULLCHAR;
12552 numlen = strlen(numtext);
12555 /* Print move number */
12556 blank = linelen > 0 && numlen > 0;
12557 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12566 fprintf(f, "%s", numtext);
12570 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12571 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12574 blank = linelen > 0 && movelen > 0;
12575 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12584 fprintf(f, "%s", move_buffer);
12585 linelen += movelen;
12587 /* [AS] Add PV info if present */
12588 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12589 /* [HGM] add time */
12590 char buf[MSG_SIZ]; int seconds;
12592 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12598 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12601 seconds = (seconds + 4)/10; // round to full seconds
12603 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12605 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12608 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12609 pvInfoList[i].score >= 0 ? "+" : "",
12610 pvInfoList[i].score / 100.0,
12611 pvInfoList[i].depth,
12614 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12616 /* Print score/depth */
12617 blank = linelen > 0 && movelen > 0;
12618 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12627 fprintf(f, "%s", move_buffer);
12628 linelen += movelen;
12634 /* Start a new line */
12635 if (linelen > 0) fprintf(f, "\n");
12637 /* Print comments after last move */
12638 if (commentList[i] != NULL) {
12639 fprintf(f, "%s\n", commentList[i]);
12643 if (gameInfo.resultDetails != NULL &&
12644 gameInfo.resultDetails[0] != NULLCHAR) {
12645 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12646 PGNResult(gameInfo.result));
12648 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12652 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12656 /* Save game in old style and close the file */
12658 SaveGameOldStyle (FILE *f)
12663 tm = time((time_t *) NULL);
12665 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12668 if (backwardMostMove > 0 || startedFromSetupPosition) {
12669 fprintf(f, "\n[--------------\n");
12670 PrintPosition(f, backwardMostMove);
12671 fprintf(f, "--------------]\n");
12676 i = backwardMostMove;
12677 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12679 while (i < forwardMostMove) {
12680 if (commentList[i] != NULL) {
12681 fprintf(f, "[%s]\n", commentList[i]);
12684 if ((i % 2) == 1) {
12685 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12688 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12690 if (commentList[i] != NULL) {
12694 if (i >= forwardMostMove) {
12698 fprintf(f, "%s\n", parseList[i]);
12703 if (commentList[i] != NULL) {
12704 fprintf(f, "[%s]\n", commentList[i]);
12707 /* This isn't really the old style, but it's close enough */
12708 if (gameInfo.resultDetails != NULL &&
12709 gameInfo.resultDetails[0] != NULLCHAR) {
12710 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12711 gameInfo.resultDetails);
12713 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12720 /* Save the current game to open file f and close the file */
12722 SaveGame (FILE *f, int dummy, char *dummy2)
12724 if (gameMode == EditPosition) EditPositionDone(TRUE);
12725 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12726 if (appData.oldSaveStyle)
12727 return SaveGameOldStyle(f);
12729 return SaveGamePGN(f);
12732 /* Save the current position to the given file */
12734 SavePositionToFile (char *filename)
12739 if (strcmp(filename, "-") == 0) {
12740 return SavePosition(stdout, 0, NULL);
12742 f = fopen(filename, "a");
12744 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12745 DisplayError(buf, errno);
12748 safeStrCpy(buf, lastMsg, MSG_SIZ);
12749 DisplayMessage(_("Waiting for access to save file"), "");
12750 flock(fileno(f), LOCK_EX); // [HGM] lock
12751 DisplayMessage(_("Saving position"), "");
12752 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12753 SavePosition(f, 0, NULL);
12754 DisplayMessage(buf, "");
12760 /* Save the current position to the given open file and close the file */
12762 SavePosition (FILE *f, int dummy, char *dummy2)
12767 if (gameMode == EditPosition) EditPositionDone(TRUE);
12768 if (appData.oldSaveStyle) {
12769 tm = time((time_t *) NULL);
12771 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12773 fprintf(f, "[--------------\n");
12774 PrintPosition(f, currentMove);
12775 fprintf(f, "--------------]\n");
12777 fen = PositionToFEN(currentMove, NULL);
12778 fprintf(f, "%s\n", fen);
12786 ReloadCmailMsgEvent (int unregister)
12789 static char *inFilename = NULL;
12790 static char *outFilename;
12792 struct stat inbuf, outbuf;
12795 /* Any registered moves are unregistered if unregister is set, */
12796 /* i.e. invoked by the signal handler */
12798 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12799 cmailMoveRegistered[i] = FALSE;
12800 if (cmailCommentList[i] != NULL) {
12801 free(cmailCommentList[i]);
12802 cmailCommentList[i] = NULL;
12805 nCmailMovesRegistered = 0;
12808 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12809 cmailResult[i] = CMAIL_NOT_RESULT;
12813 if (inFilename == NULL) {
12814 /* Because the filenames are static they only get malloced once */
12815 /* and they never get freed */
12816 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12817 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12819 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12820 sprintf(outFilename, "%s.out", appData.cmailGameName);
12823 status = stat(outFilename, &outbuf);
12825 cmailMailedMove = FALSE;
12827 status = stat(inFilename, &inbuf);
12828 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12831 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12832 counts the games, notes how each one terminated, etc.
12834 It would be nice to remove this kludge and instead gather all
12835 the information while building the game list. (And to keep it
12836 in the game list nodes instead of having a bunch of fixed-size
12837 parallel arrays.) Note this will require getting each game's
12838 termination from the PGN tags, as the game list builder does
12839 not process the game moves. --mann
12841 cmailMsgLoaded = TRUE;
12842 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12844 /* Load first game in the file or popup game menu */
12845 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12847 #endif /* !WIN32 */
12855 char string[MSG_SIZ];
12857 if ( cmailMailedMove
12858 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12859 return TRUE; /* Allow free viewing */
12862 /* Unregister move to ensure that we don't leave RegisterMove */
12863 /* with the move registered when the conditions for registering no */
12865 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12866 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12867 nCmailMovesRegistered --;
12869 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12871 free(cmailCommentList[lastLoadGameNumber - 1]);
12872 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12876 if (cmailOldMove == -1) {
12877 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12881 if (currentMove > cmailOldMove + 1) {
12882 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12886 if (currentMove < cmailOldMove) {
12887 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12891 if (forwardMostMove > currentMove) {
12892 /* Silently truncate extra moves */
12896 if ( (currentMove == cmailOldMove + 1)
12897 || ( (currentMove == cmailOldMove)
12898 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12899 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12900 if (gameInfo.result != GameUnfinished) {
12901 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12904 if (commentList[currentMove] != NULL) {
12905 cmailCommentList[lastLoadGameNumber - 1]
12906 = StrSave(commentList[currentMove]);
12908 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12910 if (appData.debugMode)
12911 fprintf(debugFP, "Saving %s for game %d\n",
12912 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12914 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12916 f = fopen(string, "w");
12917 if (appData.oldSaveStyle) {
12918 SaveGameOldStyle(f); /* also closes the file */
12920 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12921 f = fopen(string, "w");
12922 SavePosition(f, 0, NULL); /* also closes the file */
12924 fprintf(f, "{--------------\n");
12925 PrintPosition(f, currentMove);
12926 fprintf(f, "--------------}\n\n");
12928 SaveGame(f, 0, NULL); /* also closes the file*/
12931 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12932 nCmailMovesRegistered ++;
12933 } else if (nCmailGames == 1) {
12934 DisplayError(_("You have not made a move yet"), 0);
12945 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12946 FILE *commandOutput;
12947 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12948 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12954 if (! cmailMsgLoaded) {
12955 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12959 if (nCmailGames == nCmailResults) {
12960 DisplayError(_("No unfinished games"), 0);
12964 #if CMAIL_PROHIBIT_REMAIL
12965 if (cmailMailedMove) {
12966 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);
12967 DisplayError(msg, 0);
12972 if (! (cmailMailedMove || RegisterMove())) return;
12974 if ( cmailMailedMove
12975 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12976 snprintf(string, MSG_SIZ, partCommandString,
12977 appData.debugMode ? " -v" : "", appData.cmailGameName);
12978 commandOutput = popen(string, "r");
12980 if (commandOutput == NULL) {
12981 DisplayError(_("Failed to invoke cmail"), 0);
12983 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12984 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12986 if (nBuffers > 1) {
12987 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12988 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12989 nBytes = MSG_SIZ - 1;
12991 (void) memcpy(msg, buffer, nBytes);
12993 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12995 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12996 cmailMailedMove = TRUE; /* Prevent >1 moves */
12999 for (i = 0; i < nCmailGames; i ++) {
13000 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13005 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13007 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13009 appData.cmailGameName,
13011 LoadGameFromFile(buffer, 1, buffer, FALSE);
13012 cmailMsgLoaded = FALSE;
13016 DisplayInformation(msg);
13017 pclose(commandOutput);
13020 if ((*cmailMsg) != '\0') {
13021 DisplayInformation(cmailMsg);
13026 #endif /* !WIN32 */
13035 int prependComma = 0;
13037 char string[MSG_SIZ]; /* Space for game-list */
13040 if (!cmailMsgLoaded) return "";
13042 if (cmailMailedMove) {
13043 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13045 /* Create a list of games left */
13046 snprintf(string, MSG_SIZ, "[");
13047 for (i = 0; i < nCmailGames; i ++) {
13048 if (! ( cmailMoveRegistered[i]
13049 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13050 if (prependComma) {
13051 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13053 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13057 strcat(string, number);
13060 strcat(string, "]");
13062 if (nCmailMovesRegistered + nCmailResults == 0) {
13063 switch (nCmailGames) {
13065 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13069 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13073 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13078 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13080 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13085 if (nCmailResults == nCmailGames) {
13086 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13088 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13093 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13105 if (gameMode == Training)
13106 SetTrainingModeOff();
13109 cmailMsgLoaded = FALSE;
13110 if (appData.icsActive) {
13111 SendToICS(ics_prefix);
13112 SendToICS("refresh\n");
13117 ExitEvent (int status)
13121 /* Give up on clean exit */
13125 /* Keep trying for clean exit */
13129 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13131 if (telnetISR != NULL) {
13132 RemoveInputSource(telnetISR);
13134 if (icsPR != NoProc) {
13135 DestroyChildProcess(icsPR, TRUE);
13138 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13139 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13141 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13142 /* make sure this other one finishes before killing it! */
13143 if(endingGame) { int count = 0;
13144 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13145 while(endingGame && count++ < 10) DoSleep(1);
13146 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13149 /* Kill off chess programs */
13150 if (first.pr != NoProc) {
13153 DoSleep( appData.delayBeforeQuit );
13154 SendToProgram("quit\n", &first);
13155 DoSleep( appData.delayAfterQuit );
13156 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13158 if (second.pr != NoProc) {
13159 DoSleep( appData.delayBeforeQuit );
13160 SendToProgram("quit\n", &second);
13161 DoSleep( appData.delayAfterQuit );
13162 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13164 if (first.isr != NULL) {
13165 RemoveInputSource(first.isr);
13167 if (second.isr != NULL) {
13168 RemoveInputSource(second.isr);
13171 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13172 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13174 ShutDownFrontEnd();
13181 if (appData.debugMode)
13182 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13186 if (gameMode == MachinePlaysWhite ||
13187 gameMode == MachinePlaysBlack) {
13190 DisplayBothClocks();
13192 if (gameMode == PlayFromGameFile) {
13193 if (appData.timeDelay >= 0)
13194 AutoPlayGameLoop();
13195 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13196 Reset(FALSE, TRUE);
13197 SendToICS(ics_prefix);
13198 SendToICS("refresh\n");
13199 } else if (currentMove < forwardMostMove) {
13200 ForwardInner(forwardMostMove);
13202 pauseExamInvalid = FALSE;
13204 switch (gameMode) {
13208 pauseExamForwardMostMove = forwardMostMove;
13209 pauseExamInvalid = FALSE;
13212 case IcsPlayingWhite:
13213 case IcsPlayingBlack:
13217 case PlayFromGameFile:
13218 (void) StopLoadGameTimer();
13222 case BeginningOfGame:
13223 if (appData.icsActive) return;
13224 /* else fall through */
13225 case MachinePlaysWhite:
13226 case MachinePlaysBlack:
13227 case TwoMachinesPlay:
13228 if (forwardMostMove == 0)
13229 return; /* don't pause if no one has moved */
13230 if ((gameMode == MachinePlaysWhite &&
13231 !WhiteOnMove(forwardMostMove)) ||
13232 (gameMode == MachinePlaysBlack &&
13233 WhiteOnMove(forwardMostMove))) {
13244 EditCommentEvent ()
13246 char title[MSG_SIZ];
13248 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13249 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13251 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13252 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13253 parseList[currentMove - 1]);
13256 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13263 char *tags = PGNTags(&gameInfo);
13265 EditTagsPopUp(tags, NULL);
13270 AnalyzeModeEvent ()
13272 if (appData.noChessProgram || gameMode == AnalyzeMode)
13275 if (gameMode != AnalyzeFile) {
13276 if (!appData.icsEngineAnalyze) {
13278 if (gameMode != EditGame) return;
13280 ResurrectChessProgram();
13281 SendToProgram("analyze\n", &first);
13282 first.analyzing = TRUE;
13283 /*first.maybeThinking = TRUE;*/
13284 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13285 EngineOutputPopUp();
13287 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13292 StartAnalysisClock();
13293 GetTimeMark(&lastNodeCountTime);
13298 AnalyzeFileEvent ()
13300 if (appData.noChessProgram || gameMode == AnalyzeFile)
13303 if (gameMode != AnalyzeMode) {
13305 if (gameMode != EditGame) return;
13306 ResurrectChessProgram();
13307 SendToProgram("analyze\n", &first);
13308 first.analyzing = TRUE;
13309 /*first.maybeThinking = TRUE;*/
13310 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13311 EngineOutputPopUp();
13313 gameMode = AnalyzeFile;
13318 StartAnalysisClock();
13319 GetTimeMark(&lastNodeCountTime);
13321 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13325 MachineWhiteEvent ()
13328 char *bookHit = NULL;
13330 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13334 if (gameMode == PlayFromGameFile ||
13335 gameMode == TwoMachinesPlay ||
13336 gameMode == Training ||
13337 gameMode == AnalyzeMode ||
13338 gameMode == EndOfGame)
13341 if (gameMode == EditPosition)
13342 EditPositionDone(TRUE);
13344 if (!WhiteOnMove(currentMove)) {
13345 DisplayError(_("It is not White's turn"), 0);
13349 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13352 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13353 gameMode == AnalyzeFile)
13356 ResurrectChessProgram(); /* in case it isn't running */
13357 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13358 gameMode = MachinePlaysWhite;
13361 gameMode = MachinePlaysWhite;
13365 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13367 if (first.sendName) {
13368 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13369 SendToProgram(buf, &first);
13371 if (first.sendTime) {
13372 if (first.useColors) {
13373 SendToProgram("black\n", &first); /*gnu kludge*/
13375 SendTimeRemaining(&first, TRUE);
13377 if (first.useColors) {
13378 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13380 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13381 SetMachineThinkingEnables();
13382 first.maybeThinking = TRUE;
13386 if (appData.autoFlipView && !flipView) {
13387 flipView = !flipView;
13388 DrawPosition(FALSE, NULL);
13389 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13392 if(bookHit) { // [HGM] book: simulate book reply
13393 static char bookMove[MSG_SIZ]; // a bit generous?
13395 programStats.nodes = programStats.depth = programStats.time =
13396 programStats.score = programStats.got_only_move = 0;
13397 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13399 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13400 strcat(bookMove, bookHit);
13401 HandleMachineMove(bookMove, &first);
13406 MachineBlackEvent ()
13409 char *bookHit = NULL;
13411 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13415 if (gameMode == PlayFromGameFile ||
13416 gameMode == TwoMachinesPlay ||
13417 gameMode == Training ||
13418 gameMode == AnalyzeMode ||
13419 gameMode == EndOfGame)
13422 if (gameMode == EditPosition)
13423 EditPositionDone(TRUE);
13425 if (WhiteOnMove(currentMove)) {
13426 DisplayError(_("It is not Black's turn"), 0);
13430 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13433 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13434 gameMode == AnalyzeFile)
13437 ResurrectChessProgram(); /* in case it isn't running */
13438 gameMode = MachinePlaysBlack;
13442 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13444 if (first.sendName) {
13445 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13446 SendToProgram(buf, &first);
13448 if (first.sendTime) {
13449 if (first.useColors) {
13450 SendToProgram("white\n", &first); /*gnu kludge*/
13452 SendTimeRemaining(&first, FALSE);
13454 if (first.useColors) {
13455 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13457 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13458 SetMachineThinkingEnables();
13459 first.maybeThinking = TRUE;
13462 if (appData.autoFlipView && flipView) {
13463 flipView = !flipView;
13464 DrawPosition(FALSE, NULL);
13465 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13467 if(bookHit) { // [HGM] book: simulate book reply
13468 static char bookMove[MSG_SIZ]; // a bit generous?
13470 programStats.nodes = programStats.depth = programStats.time =
13471 programStats.score = programStats.got_only_move = 0;
13472 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13474 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13475 strcat(bookMove, bookHit);
13476 HandleMachineMove(bookMove, &first);
13482 DisplayTwoMachinesTitle ()
13485 if (appData.matchGames > 0) {
13486 if(appData.tourneyFile[0]) {
13487 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13488 gameInfo.white, _("vs."), gameInfo.black,
13489 nextGame+1, appData.matchGames+1,
13490 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13492 if (first.twoMachinesColor[0] == 'w') {
13493 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13494 gameInfo.white, _("vs."), gameInfo.black,
13495 first.matchWins, second.matchWins,
13496 matchGame - 1 - (first.matchWins + second.matchWins));
13498 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13499 gameInfo.white, _("vs."), gameInfo.black,
13500 second.matchWins, first.matchWins,
13501 matchGame - 1 - (first.matchWins + second.matchWins));
13504 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13510 SettingsMenuIfReady ()
13512 if (second.lastPing != second.lastPong) {
13513 DisplayMessage("", _("Waiting for second chess program"));
13514 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13518 DisplayMessage("", "");
13519 SettingsPopUp(&second);
13523 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13526 if (cps->pr == NoProc) {
13527 StartChessProgram(cps);
13528 if (cps->protocolVersion == 1) {
13531 /* kludge: allow timeout for initial "feature" command */
13533 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13534 DisplayMessage("", buf);
13535 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13543 TwoMachinesEvent P((void))
13547 ChessProgramState *onmove;
13548 char *bookHit = NULL;
13549 static int stalling = 0;
13553 if (appData.noChessProgram) return;
13555 switch (gameMode) {
13556 case TwoMachinesPlay:
13558 case MachinePlaysWhite:
13559 case MachinePlaysBlack:
13560 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13561 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13565 case BeginningOfGame:
13566 case PlayFromGameFile:
13569 if (gameMode != EditGame) return;
13572 EditPositionDone(TRUE);
13583 // forwardMostMove = currentMove;
13584 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13586 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13588 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13589 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13590 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13594 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13595 SendToProgram("force\n", &second);
13597 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13600 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13601 if(appData.matchPause>10000 || appData.matchPause<10)
13602 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13603 wait = SubtractTimeMarks(&now, &pauseStart);
13604 if(wait < appData.matchPause) {
13605 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13608 // we are now committed to starting the game
13610 DisplayMessage("", "");
13611 if (startedFromSetupPosition) {
13612 SendBoard(&second, backwardMostMove);
13613 if (appData.debugMode) {
13614 fprintf(debugFP, "Two Machines\n");
13617 for (i = backwardMostMove; i < forwardMostMove; i++) {
13618 SendMoveToProgram(i, &second);
13621 gameMode = TwoMachinesPlay;
13623 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13625 DisplayTwoMachinesTitle();
13627 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13632 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13633 SendToProgram(first.computerString, &first);
13634 if (first.sendName) {
13635 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13636 SendToProgram(buf, &first);
13638 SendToProgram(second.computerString, &second);
13639 if (second.sendName) {
13640 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13641 SendToProgram(buf, &second);
13645 if (!first.sendTime || !second.sendTime) {
13646 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13647 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13649 if (onmove->sendTime) {
13650 if (onmove->useColors) {
13651 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13653 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13655 if (onmove->useColors) {
13656 SendToProgram(onmove->twoMachinesColor, onmove);
13658 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13659 // SendToProgram("go\n", onmove);
13660 onmove->maybeThinking = TRUE;
13661 SetMachineThinkingEnables();
13665 if(bookHit) { // [HGM] book: simulate book reply
13666 static char bookMove[MSG_SIZ]; // a bit generous?
13668 programStats.nodes = programStats.depth = programStats.time =
13669 programStats.score = programStats.got_only_move = 0;
13670 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13672 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13673 strcat(bookMove, bookHit);
13674 savedMessage = bookMove; // args for deferred call
13675 savedState = onmove;
13676 ScheduleDelayedEvent(DeferredBookMove, 1);
13683 if (gameMode == Training) {
13684 SetTrainingModeOff();
13685 gameMode = PlayFromGameFile;
13686 DisplayMessage("", _("Training mode off"));
13688 gameMode = Training;
13689 animateTraining = appData.animate;
13691 /* make sure we are not already at the end of the game */
13692 if (currentMove < forwardMostMove) {
13693 SetTrainingModeOn();
13694 DisplayMessage("", _("Training mode on"));
13696 gameMode = PlayFromGameFile;
13697 DisplayError(_("Already at end of game"), 0);
13706 if (!appData.icsActive) return;
13707 switch (gameMode) {
13708 case IcsPlayingWhite:
13709 case IcsPlayingBlack:
13712 case BeginningOfGame:
13720 EditPositionDone(TRUE);
13733 gameMode = IcsIdle;
13743 switch (gameMode) {
13745 SetTrainingModeOff();
13747 case MachinePlaysWhite:
13748 case MachinePlaysBlack:
13749 case BeginningOfGame:
13750 SendToProgram("force\n", &first);
13751 SetUserThinkingEnables();
13753 case PlayFromGameFile:
13754 (void) StopLoadGameTimer();
13755 if (gameFileFP != NULL) {
13760 EditPositionDone(TRUE);
13765 SendToProgram("force\n", &first);
13767 case TwoMachinesPlay:
13768 GameEnds(EndOfFile, NULL, GE_PLAYER);
13769 ResurrectChessProgram();
13770 SetUserThinkingEnables();
13773 ResurrectChessProgram();
13775 case IcsPlayingBlack:
13776 case IcsPlayingWhite:
13777 DisplayError(_("Warning: You are still playing a game"), 0);
13780 DisplayError(_("Warning: You are still observing a game"), 0);
13783 DisplayError(_("Warning: You are still examining a game"), 0);
13794 first.offeredDraw = second.offeredDraw = 0;
13796 if (gameMode == PlayFromGameFile) {
13797 whiteTimeRemaining = timeRemaining[0][currentMove];
13798 blackTimeRemaining = timeRemaining[1][currentMove];
13802 if (gameMode == MachinePlaysWhite ||
13803 gameMode == MachinePlaysBlack ||
13804 gameMode == TwoMachinesPlay ||
13805 gameMode == EndOfGame) {
13806 i = forwardMostMove;
13807 while (i > currentMove) {
13808 SendToProgram("undo\n", &first);
13811 if(!adjustedClock) {
13812 whiteTimeRemaining = timeRemaining[0][currentMove];
13813 blackTimeRemaining = timeRemaining[1][currentMove];
13814 DisplayBothClocks();
13816 if (whiteFlag || blackFlag) {
13817 whiteFlag = blackFlag = 0;
13822 gameMode = EditGame;
13829 EditPositionEvent ()
13831 if (gameMode == EditPosition) {
13837 if (gameMode != EditGame) return;
13839 gameMode = EditPosition;
13842 if (currentMove > 0)
13843 CopyBoard(boards[0], boards[currentMove]);
13845 blackPlaysFirst = !WhiteOnMove(currentMove);
13847 currentMove = forwardMostMove = backwardMostMove = 0;
13848 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13850 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13856 /* [DM] icsEngineAnalyze - possible call from other functions */
13857 if (appData.icsEngineAnalyze) {
13858 appData.icsEngineAnalyze = FALSE;
13860 DisplayMessage("",_("Close ICS engine analyze..."));
13862 if (first.analysisSupport && first.analyzing) {
13863 SendToProgram("exit\n", &first);
13864 first.analyzing = FALSE;
13866 thinkOutput[0] = NULLCHAR;
13870 EditPositionDone (Boolean fakeRights)
13872 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13874 startedFromSetupPosition = TRUE;
13875 InitChessProgram(&first, FALSE);
13876 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13877 boards[0][EP_STATUS] = EP_NONE;
13878 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13879 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13880 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13881 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13882 } else boards[0][CASTLING][2] = NoRights;
13883 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13884 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13885 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13886 } else boards[0][CASTLING][5] = NoRights;
13888 SendToProgram("force\n", &first);
13889 if (blackPlaysFirst) {
13890 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13891 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13892 currentMove = forwardMostMove = backwardMostMove = 1;
13893 CopyBoard(boards[1], boards[0]);
13895 currentMove = forwardMostMove = backwardMostMove = 0;
13897 SendBoard(&first, forwardMostMove);
13898 if (appData.debugMode) {
13899 fprintf(debugFP, "EditPosDone\n");
13902 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13903 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13904 gameMode = EditGame;
13906 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13907 ClearHighlights(); /* [AS] */
13910 /* Pause for `ms' milliseconds */
13911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13913 TimeDelay (long ms)
13920 } while (SubtractTimeMarks(&m2, &m1) < ms);
13923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13925 SendMultiLineToICS (char *buf)
13927 char temp[MSG_SIZ+1], *p;
13934 strncpy(temp, buf, len);
13939 if (*p == '\n' || *p == '\r')
13944 strcat(temp, "\n");
13946 SendToPlayer(temp, strlen(temp));
13950 SetWhiteToPlayEvent ()
13952 if (gameMode == EditPosition) {
13953 blackPlaysFirst = FALSE;
13954 DisplayBothClocks(); /* works because currentMove is 0 */
13955 } else if (gameMode == IcsExamining) {
13956 SendToICS(ics_prefix);
13957 SendToICS("tomove white\n");
13962 SetBlackToPlayEvent ()
13964 if (gameMode == EditPosition) {
13965 blackPlaysFirst = TRUE;
13966 currentMove = 1; /* kludge */
13967 DisplayBothClocks();
13969 } else if (gameMode == IcsExamining) {
13970 SendToICS(ics_prefix);
13971 SendToICS("tomove black\n");
13976 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13979 ChessSquare piece = boards[0][y][x];
13981 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13983 switch (selection) {
13985 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13986 SendToICS(ics_prefix);
13987 SendToICS("bsetup clear\n");
13988 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13989 SendToICS(ics_prefix);
13990 SendToICS("clearboard\n");
13992 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13993 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13994 for (y = 0; y < BOARD_HEIGHT; y++) {
13995 if (gameMode == IcsExamining) {
13996 if (boards[currentMove][y][x] != EmptySquare) {
13997 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14002 boards[0][y][x] = p;
14007 if (gameMode == EditPosition) {
14008 DrawPosition(FALSE, boards[0]);
14013 SetWhiteToPlayEvent();
14017 SetBlackToPlayEvent();
14021 if (gameMode == IcsExamining) {
14022 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14023 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14026 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14027 if(x == BOARD_LEFT-2) {
14028 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14029 boards[0][y][1] = 0;
14031 if(x == BOARD_RGHT+1) {
14032 if(y >= gameInfo.holdingsSize) break;
14033 boards[0][y][BOARD_WIDTH-2] = 0;
14036 boards[0][y][x] = EmptySquare;
14037 DrawPosition(FALSE, boards[0]);
14042 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14043 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14044 selection = (ChessSquare) (PROMOTED piece);
14045 } else if(piece == EmptySquare) selection = WhiteSilver;
14046 else selection = (ChessSquare)((int)piece - 1);
14050 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14051 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14052 selection = (ChessSquare) (DEMOTED piece);
14053 } else if(piece == EmptySquare) selection = BlackSilver;
14054 else selection = (ChessSquare)((int)piece + 1);
14059 if(gameInfo.variant == VariantShatranj ||
14060 gameInfo.variant == VariantXiangqi ||
14061 gameInfo.variant == VariantCourier ||
14062 gameInfo.variant == VariantMakruk )
14063 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14068 if(gameInfo.variant == VariantXiangqi)
14069 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14070 if(gameInfo.variant == VariantKnightmate)
14071 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14074 if (gameMode == IcsExamining) {
14075 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14076 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14077 PieceToChar(selection), AAA + x, ONE + y);
14080 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14082 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14083 n = PieceToNumber(selection - BlackPawn);
14084 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14085 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14086 boards[0][BOARD_HEIGHT-1-n][1]++;
14088 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14089 n = PieceToNumber(selection);
14090 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14091 boards[0][n][BOARD_WIDTH-1] = selection;
14092 boards[0][n][BOARD_WIDTH-2]++;
14095 boards[0][y][x] = selection;
14096 DrawPosition(TRUE, boards[0]);
14098 fromX = fromY = -1;
14106 DropMenuEvent (ChessSquare selection, int x, int y)
14108 ChessMove moveType;
14110 switch (gameMode) {
14111 case IcsPlayingWhite:
14112 case MachinePlaysBlack:
14113 if (!WhiteOnMove(currentMove)) {
14114 DisplayMoveError(_("It is Black's turn"));
14117 moveType = WhiteDrop;
14119 case IcsPlayingBlack:
14120 case MachinePlaysWhite:
14121 if (WhiteOnMove(currentMove)) {
14122 DisplayMoveError(_("It is White's turn"));
14125 moveType = BlackDrop;
14128 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14134 if (moveType == BlackDrop && selection < BlackPawn) {
14135 selection = (ChessSquare) ((int) selection
14136 + (int) BlackPawn - (int) WhitePawn);
14138 if (boards[currentMove][y][x] != EmptySquare) {
14139 DisplayMoveError(_("That square is occupied"));
14143 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14149 /* Accept a pending offer of any kind from opponent */
14151 if (appData.icsActive) {
14152 SendToICS(ics_prefix);
14153 SendToICS("accept\n");
14154 } else if (cmailMsgLoaded) {
14155 if (currentMove == cmailOldMove &&
14156 commentList[cmailOldMove] != NULL &&
14157 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14158 "Black offers a draw" : "White offers a draw")) {
14160 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14161 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14163 DisplayError(_("There is no pending offer on this move"), 0);
14164 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14167 /* Not used for offers from chess program */
14174 /* Decline a pending offer of any kind from opponent */
14176 if (appData.icsActive) {
14177 SendToICS(ics_prefix);
14178 SendToICS("decline\n");
14179 } else if (cmailMsgLoaded) {
14180 if (currentMove == cmailOldMove &&
14181 commentList[cmailOldMove] != NULL &&
14182 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14183 "Black offers a draw" : "White offers a draw")) {
14185 AppendComment(cmailOldMove, "Draw declined", TRUE);
14186 DisplayComment(cmailOldMove - 1, "Draw declined");
14189 DisplayError(_("There is no pending offer on this move"), 0);
14192 /* Not used for offers from chess program */
14199 /* Issue ICS rematch command */
14200 if (appData.icsActive) {
14201 SendToICS(ics_prefix);
14202 SendToICS("rematch\n");
14209 /* Call your opponent's flag (claim a win on time) */
14210 if (appData.icsActive) {
14211 SendToICS(ics_prefix);
14212 SendToICS("flag\n");
14214 switch (gameMode) {
14217 case MachinePlaysWhite:
14220 GameEnds(GameIsDrawn, "Both players ran out of time",
14223 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14225 DisplayError(_("Your opponent is not out of time"), 0);
14228 case MachinePlaysBlack:
14231 GameEnds(GameIsDrawn, "Both players ran out of time",
14234 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14236 DisplayError(_("Your opponent is not out of time"), 0);
14244 ClockClick (int which)
14245 { // [HGM] code moved to back-end from winboard.c
14246 if(which) { // black clock
14247 if (gameMode == EditPosition || gameMode == IcsExamining) {
14248 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14249 SetBlackToPlayEvent();
14250 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14251 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14252 } else if (shiftKey) {
14253 AdjustClock(which, -1);
14254 } else if (gameMode == IcsPlayingWhite ||
14255 gameMode == MachinePlaysBlack) {
14258 } else { // white clock
14259 if (gameMode == EditPosition || gameMode == IcsExamining) {
14260 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14261 SetWhiteToPlayEvent();
14262 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14263 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14264 } else if (shiftKey) {
14265 AdjustClock(which, -1);
14266 } else if (gameMode == IcsPlayingBlack ||
14267 gameMode == MachinePlaysWhite) {
14276 /* Offer draw or accept pending draw offer from opponent */
14278 if (appData.icsActive) {
14279 /* Note: tournament rules require draw offers to be
14280 made after you make your move but before you punch
14281 your clock. Currently ICS doesn't let you do that;
14282 instead, you immediately punch your clock after making
14283 a move, but you can offer a draw at any time. */
14285 SendToICS(ics_prefix);
14286 SendToICS("draw\n");
14287 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14288 } else if (cmailMsgLoaded) {
14289 if (currentMove == cmailOldMove &&
14290 commentList[cmailOldMove] != NULL &&
14291 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14292 "Black offers a draw" : "White offers a draw")) {
14293 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14294 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14295 } else if (currentMove == cmailOldMove + 1) {
14296 char *offer = WhiteOnMove(cmailOldMove) ?
14297 "White offers a draw" : "Black offers a draw";
14298 AppendComment(currentMove, offer, TRUE);
14299 DisplayComment(currentMove - 1, offer);
14300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14302 DisplayError(_("You must make your move before offering a draw"), 0);
14303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14305 } else if (first.offeredDraw) {
14306 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14308 if (first.sendDrawOffers) {
14309 SendToProgram("draw\n", &first);
14310 userOfferedDraw = TRUE;
14318 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14320 if (appData.icsActive) {
14321 SendToICS(ics_prefix);
14322 SendToICS("adjourn\n");
14324 /* Currently GNU Chess doesn't offer or accept Adjourns */
14332 /* Offer Abort or accept pending Abort offer from opponent */
14334 if (appData.icsActive) {
14335 SendToICS(ics_prefix);
14336 SendToICS("abort\n");
14338 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14345 /* Resign. You can do this even if it's not your turn. */
14347 if (appData.icsActive) {
14348 SendToICS(ics_prefix);
14349 SendToICS("resign\n");
14351 switch (gameMode) {
14352 case MachinePlaysWhite:
14353 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14355 case MachinePlaysBlack:
14356 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14359 if (cmailMsgLoaded) {
14361 if (WhiteOnMove(cmailOldMove)) {
14362 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14364 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14366 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14377 StopObservingEvent ()
14379 /* Stop observing current games */
14380 SendToICS(ics_prefix);
14381 SendToICS("unobserve\n");
14385 StopExaminingEvent ()
14387 /* Stop observing current game */
14388 SendToICS(ics_prefix);
14389 SendToICS("unexamine\n");
14393 ForwardInner (int target)
14395 int limit; int oldSeekGraphUp = seekGraphUp;
14397 if (appData.debugMode)
14398 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14399 target, currentMove, forwardMostMove);
14401 if (gameMode == EditPosition)
14404 seekGraphUp = FALSE;
14405 MarkTargetSquares(1);
14407 if (gameMode == PlayFromGameFile && !pausing)
14410 if (gameMode == IcsExamining && pausing)
14411 limit = pauseExamForwardMostMove;
14413 limit = forwardMostMove;
14415 if (target > limit) target = limit;
14417 if (target > 0 && moveList[target - 1][0]) {
14418 int fromX, fromY, toX, toY;
14419 toX = moveList[target - 1][2] - AAA;
14420 toY = moveList[target - 1][3] - ONE;
14421 if (moveList[target - 1][1] == '@') {
14422 if (appData.highlightLastMove) {
14423 SetHighlights(-1, -1, toX, toY);
14426 fromX = moveList[target - 1][0] - AAA;
14427 fromY = moveList[target - 1][1] - ONE;
14428 if (target == currentMove + 1) {
14429 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14431 if (appData.highlightLastMove) {
14432 SetHighlights(fromX, fromY, toX, toY);
14436 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14437 gameMode == Training || gameMode == PlayFromGameFile ||
14438 gameMode == AnalyzeFile) {
14439 while (currentMove < target) {
14440 SendMoveToProgram(currentMove++, &first);
14443 currentMove = target;
14446 if (gameMode == EditGame || gameMode == EndOfGame) {
14447 whiteTimeRemaining = timeRemaining[0][currentMove];
14448 blackTimeRemaining = timeRemaining[1][currentMove];
14450 DisplayBothClocks();
14451 DisplayMove(currentMove - 1);
14452 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14453 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14454 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14455 DisplayComment(currentMove - 1, commentList[currentMove]);
14457 ClearMap(); // [HGM] exclude: invalidate map
14464 if (gameMode == IcsExamining && !pausing) {
14465 SendToICS(ics_prefix);
14466 SendToICS("forward\n");
14468 ForwardInner(currentMove + 1);
14475 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14476 /* to optimze, we temporarily turn off analysis mode while we feed
14477 * the remaining moves to the engine. Otherwise we get analysis output
14480 if (first.analysisSupport) {
14481 SendToProgram("exit\nforce\n", &first);
14482 first.analyzing = FALSE;
14486 if (gameMode == IcsExamining && !pausing) {
14487 SendToICS(ics_prefix);
14488 SendToICS("forward 999999\n");
14490 ForwardInner(forwardMostMove);
14493 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14494 /* we have fed all the moves, so reactivate analysis mode */
14495 SendToProgram("analyze\n", &first);
14496 first.analyzing = TRUE;
14497 /*first.maybeThinking = TRUE;*/
14498 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14503 BackwardInner (int target)
14505 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14507 if (appData.debugMode)
14508 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14509 target, currentMove, forwardMostMove);
14511 if (gameMode == EditPosition) return;
14512 seekGraphUp = FALSE;
14513 MarkTargetSquares(1);
14514 if (currentMove <= backwardMostMove) {
14516 DrawPosition(full_redraw, boards[currentMove]);
14519 if (gameMode == PlayFromGameFile && !pausing)
14522 if (moveList[target][0]) {
14523 int fromX, fromY, toX, toY;
14524 toX = moveList[target][2] - AAA;
14525 toY = moveList[target][3] - ONE;
14526 if (moveList[target][1] == '@') {
14527 if (appData.highlightLastMove) {
14528 SetHighlights(-1, -1, toX, toY);
14531 fromX = moveList[target][0] - AAA;
14532 fromY = moveList[target][1] - ONE;
14533 if (target == currentMove - 1) {
14534 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14536 if (appData.highlightLastMove) {
14537 SetHighlights(fromX, fromY, toX, toY);
14541 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14542 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14543 while (currentMove > target) {
14544 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14545 // null move cannot be undone. Reload program with move history before it.
14547 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14548 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14550 SendBoard(&first, i);
14551 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14554 SendToProgram("undo\n", &first);
14558 currentMove = target;
14561 if (gameMode == EditGame || gameMode == EndOfGame) {
14562 whiteTimeRemaining = timeRemaining[0][currentMove];
14563 blackTimeRemaining = timeRemaining[1][currentMove];
14565 DisplayBothClocks();
14566 DisplayMove(currentMove - 1);
14567 DrawPosition(full_redraw, boards[currentMove]);
14568 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14569 // [HGM] PV info: routine tests if comment empty
14570 DisplayComment(currentMove - 1, commentList[currentMove]);
14571 ClearMap(); // [HGM] exclude: invalidate map
14577 if (gameMode == IcsExamining && !pausing) {
14578 SendToICS(ics_prefix);
14579 SendToICS("backward\n");
14581 BackwardInner(currentMove - 1);
14588 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14589 /* to optimize, we temporarily turn off analysis mode while we undo
14590 * all the moves. Otherwise we get analysis output after each undo.
14592 if (first.analysisSupport) {
14593 SendToProgram("exit\nforce\n", &first);
14594 first.analyzing = FALSE;
14598 if (gameMode == IcsExamining && !pausing) {
14599 SendToICS(ics_prefix);
14600 SendToICS("backward 999999\n");
14602 BackwardInner(backwardMostMove);
14605 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14606 /* we have fed all the moves, so reactivate analysis mode */
14607 SendToProgram("analyze\n", &first);
14608 first.analyzing = TRUE;
14609 /*first.maybeThinking = TRUE;*/
14610 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14617 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14618 if (to >= forwardMostMove) to = forwardMostMove;
14619 if (to <= backwardMostMove) to = backwardMostMove;
14620 if (to < currentMove) {
14628 RevertEvent (Boolean annotate)
14630 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14633 if (gameMode != IcsExamining) {
14634 DisplayError(_("You are not examining a game"), 0);
14638 DisplayError(_("You can't revert while pausing"), 0);
14641 SendToICS(ics_prefix);
14642 SendToICS("revert\n");
14646 RetractMoveEvent ()
14648 switch (gameMode) {
14649 case MachinePlaysWhite:
14650 case MachinePlaysBlack:
14651 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14652 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14655 if (forwardMostMove < 2) return;
14656 currentMove = forwardMostMove = forwardMostMove - 2;
14657 whiteTimeRemaining = timeRemaining[0][currentMove];
14658 blackTimeRemaining = timeRemaining[1][currentMove];
14659 DisplayBothClocks();
14660 DisplayMove(currentMove - 1);
14661 ClearHighlights();/*!! could figure this out*/
14662 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14663 SendToProgram("remove\n", &first);
14664 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14667 case BeginningOfGame:
14671 case IcsPlayingWhite:
14672 case IcsPlayingBlack:
14673 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14674 SendToICS(ics_prefix);
14675 SendToICS("takeback 2\n");
14677 SendToICS(ics_prefix);
14678 SendToICS("takeback 1\n");
14687 ChessProgramState *cps;
14689 switch (gameMode) {
14690 case MachinePlaysWhite:
14691 if (!WhiteOnMove(forwardMostMove)) {
14692 DisplayError(_("It is your turn"), 0);
14697 case MachinePlaysBlack:
14698 if (WhiteOnMove(forwardMostMove)) {
14699 DisplayError(_("It is your turn"), 0);
14704 case TwoMachinesPlay:
14705 if (WhiteOnMove(forwardMostMove) ==
14706 (first.twoMachinesColor[0] == 'w')) {
14712 case BeginningOfGame:
14716 SendToProgram("?\n", cps);
14720 TruncateGameEvent ()
14723 if (gameMode != EditGame) return;
14730 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14731 if (forwardMostMove > currentMove) {
14732 if (gameInfo.resultDetails != NULL) {
14733 free(gameInfo.resultDetails);
14734 gameInfo.resultDetails = NULL;
14735 gameInfo.result = GameUnfinished;
14737 forwardMostMove = currentMove;
14738 HistorySet(parseList, backwardMostMove, forwardMostMove,
14746 if (appData.noChessProgram) return;
14747 switch (gameMode) {
14748 case MachinePlaysWhite:
14749 if (WhiteOnMove(forwardMostMove)) {
14750 DisplayError(_("Wait until your turn"), 0);
14754 case BeginningOfGame:
14755 case MachinePlaysBlack:
14756 if (!WhiteOnMove(forwardMostMove)) {
14757 DisplayError(_("Wait until your turn"), 0);
14762 DisplayError(_("No hint available"), 0);
14765 SendToProgram("hint\n", &first);
14766 hintRequested = TRUE;
14772 if (appData.noChessProgram) return;
14773 switch (gameMode) {
14774 case MachinePlaysWhite:
14775 if (WhiteOnMove(forwardMostMove)) {
14776 DisplayError(_("Wait until your turn"), 0);
14780 case BeginningOfGame:
14781 case MachinePlaysBlack:
14782 if (!WhiteOnMove(forwardMostMove)) {
14783 DisplayError(_("Wait until your turn"), 0);
14788 EditPositionDone(TRUE);
14790 case TwoMachinesPlay:
14795 SendToProgram("bk\n", &first);
14796 bookOutput[0] = NULLCHAR;
14797 bookRequested = TRUE;
14803 char *tags = PGNTags(&gameInfo);
14804 TagsPopUp(tags, CmailMsg());
14808 /* end button procedures */
14811 PrintPosition (FILE *fp, int move)
14815 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14816 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14817 char c = PieceToChar(boards[move][i][j]);
14818 fputc(c == 'x' ? '.' : c, fp);
14819 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14822 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14823 fprintf(fp, "white to play\n");
14825 fprintf(fp, "black to play\n");
14829 PrintOpponents (FILE *fp)
14831 if (gameInfo.white != NULL) {
14832 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14838 /* Find last component of program's own name, using some heuristics */
14840 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14843 int local = (strcmp(host, "localhost") == 0);
14844 while (!local && (p = strchr(prog, ';')) != NULL) {
14846 while (*p == ' ') p++;
14849 if (*prog == '"' || *prog == '\'') {
14850 q = strchr(prog + 1, *prog);
14852 q = strchr(prog, ' ');
14854 if (q == NULL) q = prog + strlen(prog);
14856 while (p >= prog && *p != '/' && *p != '\\') p--;
14858 if(p == prog && *p == '"') p++;
14860 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14861 memcpy(buf, p, q - p);
14862 buf[q - p] = NULLCHAR;
14870 TimeControlTagValue ()
14873 if (!appData.clockMode) {
14874 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14875 } else if (movesPerSession > 0) {
14876 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14877 } else if (timeIncrement == 0) {
14878 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14880 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14882 return StrSave(buf);
14888 /* This routine is used only for certain modes */
14889 VariantClass v = gameInfo.variant;
14890 ChessMove r = GameUnfinished;
14893 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14894 r = gameInfo.result;
14895 p = gameInfo.resultDetails;
14896 gameInfo.resultDetails = NULL;
14898 ClearGameInfo(&gameInfo);
14899 gameInfo.variant = v;
14901 switch (gameMode) {
14902 case MachinePlaysWhite:
14903 gameInfo.event = StrSave( appData.pgnEventHeader );
14904 gameInfo.site = StrSave(HostName());
14905 gameInfo.date = PGNDate();
14906 gameInfo.round = StrSave("-");
14907 gameInfo.white = StrSave(first.tidy);
14908 gameInfo.black = StrSave(UserName());
14909 gameInfo.timeControl = TimeControlTagValue();
14912 case MachinePlaysBlack:
14913 gameInfo.event = StrSave( appData.pgnEventHeader );
14914 gameInfo.site = StrSave(HostName());
14915 gameInfo.date = PGNDate();
14916 gameInfo.round = StrSave("-");
14917 gameInfo.white = StrSave(UserName());
14918 gameInfo.black = StrSave(first.tidy);
14919 gameInfo.timeControl = TimeControlTagValue();
14922 case TwoMachinesPlay:
14923 gameInfo.event = StrSave( appData.pgnEventHeader );
14924 gameInfo.site = StrSave(HostName());
14925 gameInfo.date = PGNDate();
14928 snprintf(buf, MSG_SIZ, "%d", roundNr);
14929 gameInfo.round = StrSave(buf);
14931 gameInfo.round = StrSave("-");
14933 if (first.twoMachinesColor[0] == 'w') {
14934 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14935 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14937 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14938 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14940 gameInfo.timeControl = TimeControlTagValue();
14944 gameInfo.event = StrSave("Edited game");
14945 gameInfo.site = StrSave(HostName());
14946 gameInfo.date = PGNDate();
14947 gameInfo.round = StrSave("-");
14948 gameInfo.white = StrSave("-");
14949 gameInfo.black = StrSave("-");
14950 gameInfo.result = r;
14951 gameInfo.resultDetails = p;
14955 gameInfo.event = StrSave("Edited position");
14956 gameInfo.site = StrSave(HostName());
14957 gameInfo.date = PGNDate();
14958 gameInfo.round = StrSave("-");
14959 gameInfo.white = StrSave("-");
14960 gameInfo.black = StrSave("-");
14963 case IcsPlayingWhite:
14964 case IcsPlayingBlack:
14969 case PlayFromGameFile:
14970 gameInfo.event = StrSave("Game from non-PGN file");
14971 gameInfo.site = StrSave(HostName());
14972 gameInfo.date = PGNDate();
14973 gameInfo.round = StrSave("-");
14974 gameInfo.white = StrSave("?");
14975 gameInfo.black = StrSave("?");
14984 ReplaceComment (int index, char *text)
14990 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14991 pvInfoList[index-1].depth == len &&
14992 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14993 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14994 while (*text == '\n') text++;
14995 len = strlen(text);
14996 while (len > 0 && text[len - 1] == '\n') len--;
14998 if (commentList[index] != NULL)
14999 free(commentList[index]);
15002 commentList[index] = NULL;
15005 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15006 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15007 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15008 commentList[index] = (char *) malloc(len + 2);
15009 strncpy(commentList[index], text, len);
15010 commentList[index][len] = '\n';
15011 commentList[index][len + 1] = NULLCHAR;
15013 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15015 commentList[index] = (char *) malloc(len + 7);
15016 safeStrCpy(commentList[index], "{\n", 3);
15017 safeStrCpy(commentList[index]+2, text, len+1);
15018 commentList[index][len+2] = NULLCHAR;
15019 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15020 strcat(commentList[index], "\n}\n");
15025 CrushCRs (char *text)
15033 if (ch == '\r') continue;
15035 } while (ch != '\0');
15039 AppendComment (int index, char *text, Boolean addBraces)
15040 /* addBraces tells if we should add {} */
15045 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15046 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15049 while (*text == '\n') text++;
15050 len = strlen(text);
15051 while (len > 0 && text[len - 1] == '\n') len--;
15052 text[len] = NULLCHAR;
15054 if (len == 0) return;
15056 if (commentList[index] != NULL) {
15057 Boolean addClosingBrace = addBraces;
15058 old = commentList[index];
15059 oldlen = strlen(old);
15060 while(commentList[index][oldlen-1] == '\n')
15061 commentList[index][--oldlen] = NULLCHAR;
15062 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15063 safeStrCpy(commentList[index], old, oldlen + len + 6);
15065 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15066 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15067 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15068 while (*text == '\n') { text++; len--; }
15069 commentList[index][--oldlen] = NULLCHAR;
15071 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15072 else strcat(commentList[index], "\n");
15073 strcat(commentList[index], text);
15074 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15075 else strcat(commentList[index], "\n");
15077 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15079 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15080 else commentList[index][0] = NULLCHAR;
15081 strcat(commentList[index], text);
15082 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15083 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15088 FindStr (char * text, char * sub_text)
15090 char * result = strstr( text, sub_text );
15092 if( result != NULL ) {
15093 result += strlen( sub_text );
15099 /* [AS] Try to extract PV info from PGN comment */
15100 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15102 GetInfoFromComment (int index, char * text)
15104 char * sep = text, *p;
15106 if( text != NULL && index > 0 ) {
15109 int time = -1, sec = 0, deci;
15110 char * s_eval = FindStr( text, "[%eval " );
15111 char * s_emt = FindStr( text, "[%emt " );
15113 if( s_eval != NULL || s_emt != NULL ) {
15117 if( s_eval != NULL ) {
15118 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15122 if( delim != ']' ) {
15127 if( s_emt != NULL ) {
15132 /* We expect something like: [+|-]nnn.nn/dd */
15135 if(*text != '{') return text; // [HGM] braces: must be normal comment
15137 sep = strchr( text, '/' );
15138 if( sep == NULL || sep < (text+4) ) {
15143 if(p[1] == '(') { // comment starts with PV
15144 p = strchr(p, ')'); // locate end of PV
15145 if(p == NULL || sep < p+5) return text;
15146 // at this point we have something like "{(.*) +0.23/6 ..."
15147 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15148 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15149 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15151 time = -1; sec = -1; deci = -1;
15152 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15153 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15154 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15155 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15159 if( score_lo < 0 || score_lo >= 100 ) {
15163 if(sec >= 0) time = 600*time + 10*sec; else
15164 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15166 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15168 /* [HGM] PV time: now locate end of PV info */
15169 while( *++sep >= '0' && *sep <= '9'); // strip depth
15171 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15173 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15175 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15176 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15187 pvInfoList[index-1].depth = depth;
15188 pvInfoList[index-1].score = score;
15189 pvInfoList[index-1].time = 10*time; // centi-sec
15190 if(*sep == '}') *sep = 0; else *--sep = '{';
15191 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15197 SendToProgram (char *message, ChessProgramState *cps)
15199 int count, outCount, error;
15202 if (cps->pr == NoProc) return;
15205 if (appData.debugMode) {
15208 fprintf(debugFP, "%ld >%-6s: %s",
15209 SubtractTimeMarks(&now, &programStartTime),
15210 cps->which, message);
15212 fprintf(serverFP, "%ld >%-6s: %s",
15213 SubtractTimeMarks(&now, &programStartTime),
15214 cps->which, message), fflush(serverFP);
15217 count = strlen(message);
15218 outCount = OutputToProcess(cps->pr, message, count, &error);
15219 if (outCount < count && !exiting
15220 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15221 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15222 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15223 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15224 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15225 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15226 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15227 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15229 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15230 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15231 gameInfo.result = res;
15233 gameInfo.resultDetails = StrSave(buf);
15235 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15236 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15241 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15245 ChessProgramState *cps = (ChessProgramState *)closure;
15247 if (isr != cps->isr) return; /* Killed intentionally */
15250 RemoveInputSource(cps->isr);
15251 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15252 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15253 _(cps->which), cps->program);
15254 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15255 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15256 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15257 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15258 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15260 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15261 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15262 gameInfo.result = res;
15264 gameInfo.resultDetails = StrSave(buf);
15266 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15267 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15269 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15270 _(cps->which), cps->program);
15271 RemoveInputSource(cps->isr);
15273 /* [AS] Program is misbehaving badly... kill it */
15274 if( count == -2 ) {
15275 DestroyChildProcess( cps->pr, 9 );
15279 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15284 if ((end_str = strchr(message, '\r')) != NULL)
15285 *end_str = NULLCHAR;
15286 if ((end_str = strchr(message, '\n')) != NULL)
15287 *end_str = NULLCHAR;
15289 if (appData.debugMode) {
15290 TimeMark now; int print = 1;
15291 char *quote = ""; char c; int i;
15293 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15294 char start = message[0];
15295 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15296 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15297 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15298 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15299 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15300 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15301 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15302 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15303 sscanf(message, "hint: %c", &c)!=1 &&
15304 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15305 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15306 print = (appData.engineComments >= 2);
15308 message[0] = start; // restore original message
15312 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15313 SubtractTimeMarks(&now, &programStartTime), cps->which,
15317 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15318 SubtractTimeMarks(&now, &programStartTime), cps->which,
15320 message), fflush(serverFP);
15324 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15325 if (appData.icsEngineAnalyze) {
15326 if (strstr(message, "whisper") != NULL ||
15327 strstr(message, "kibitz") != NULL ||
15328 strstr(message, "tellics") != NULL) return;
15331 HandleMachineMove(message, cps);
15336 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15341 if( timeControl_2 > 0 ) {
15342 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15343 tc = timeControl_2;
15346 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15347 inc /= cps->timeOdds;
15348 st /= cps->timeOdds;
15350 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15353 /* Set exact time per move, normally using st command */
15354 if (cps->stKludge) {
15355 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15357 if (seconds == 0) {
15358 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15360 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15363 snprintf(buf, MSG_SIZ, "st %d\n", st);
15366 /* Set conventional or incremental time control, using level command */
15367 if (seconds == 0) {
15368 /* Note old gnuchess bug -- minutes:seconds used to not work.
15369 Fixed in later versions, but still avoid :seconds
15370 when seconds is 0. */
15371 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15373 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15374 seconds, inc/1000.);
15377 SendToProgram(buf, cps);
15379 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15380 /* Orthogonally, limit search to given depth */
15382 if (cps->sdKludge) {
15383 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15385 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15387 SendToProgram(buf, cps);
15390 if(cps->nps >= 0) { /* [HGM] nps */
15391 if(cps->supportsNPS == FALSE)
15392 cps->nps = -1; // don't use if engine explicitly says not supported!
15394 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15395 SendToProgram(buf, cps);
15400 ChessProgramState *
15402 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15404 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15405 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15411 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15413 char message[MSG_SIZ];
15416 /* Note: this routine must be called when the clocks are stopped
15417 or when they have *just* been set or switched; otherwise
15418 it will be off by the time since the current tick started.
15420 if (machineWhite) {
15421 time = whiteTimeRemaining / 10;
15422 otime = blackTimeRemaining / 10;
15424 time = blackTimeRemaining / 10;
15425 otime = whiteTimeRemaining / 10;
15427 /* [HGM] translate opponent's time by time-odds factor */
15428 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15430 if (time <= 0) time = 1;
15431 if (otime <= 0) otime = 1;
15433 snprintf(message, MSG_SIZ, "time %ld\n", time);
15434 SendToProgram(message, cps);
15436 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15437 SendToProgram(message, cps);
15441 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15444 int len = strlen(name);
15447 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15449 sscanf(*p, "%d", &val);
15451 while (**p && **p != ' ')
15453 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15454 SendToProgram(buf, cps);
15461 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15464 int len = strlen(name);
15465 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15467 sscanf(*p, "%d", loc);
15468 while (**p && **p != ' ') (*p)++;
15469 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15470 SendToProgram(buf, cps);
15477 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15480 int len = strlen(name);
15481 if (strncmp((*p), name, len) == 0
15482 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15484 sscanf(*p, "%[^\"]", loc);
15485 while (**p && **p != '\"') (*p)++;
15486 if (**p == '\"') (*p)++;
15487 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15488 SendToProgram(buf, cps);
15495 ParseOption (Option *opt, ChessProgramState *cps)
15496 // [HGM] options: process the string that defines an engine option, and determine
15497 // name, type, default value, and allowed value range
15499 char *p, *q, buf[MSG_SIZ];
15500 int n, min = (-1)<<31, max = 1<<31, def;
15502 if(p = strstr(opt->name, " -spin ")) {
15503 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15504 if(max < min) max = min; // enforce consistency
15505 if(def < min) def = min;
15506 if(def > max) def = max;
15511 } else if((p = strstr(opt->name, " -slider "))) {
15512 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15513 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15514 if(max < min) max = min; // enforce consistency
15515 if(def < min) def = min;
15516 if(def > max) def = max;
15520 opt->type = Spin; // Slider;
15521 } else if((p = strstr(opt->name, " -string "))) {
15522 opt->textValue = p+9;
15523 opt->type = TextBox;
15524 } else if((p = strstr(opt->name, " -file "))) {
15525 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15526 opt->textValue = p+7;
15527 opt->type = FileName; // FileName;
15528 } else if((p = strstr(opt->name, " -path "))) {
15529 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15530 opt->textValue = p+7;
15531 opt->type = PathName; // PathName;
15532 } else if(p = strstr(opt->name, " -check ")) {
15533 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15534 opt->value = (def != 0);
15535 opt->type = CheckBox;
15536 } else if(p = strstr(opt->name, " -combo ")) {
15537 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15538 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15539 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15540 opt->value = n = 0;
15541 while(q = StrStr(q, " /// ")) {
15542 n++; *q = 0; // count choices, and null-terminate each of them
15544 if(*q == '*') { // remember default, which is marked with * prefix
15548 cps->comboList[cps->comboCnt++] = q;
15550 cps->comboList[cps->comboCnt++] = NULL;
15552 opt->type = ComboBox;
15553 } else if(p = strstr(opt->name, " -button")) {
15554 opt->type = Button;
15555 } else if(p = strstr(opt->name, " -save")) {
15556 opt->type = SaveButton;
15557 } else return FALSE;
15558 *p = 0; // terminate option name
15559 // now look if the command-line options define a setting for this engine option.
15560 if(cps->optionSettings && cps->optionSettings[0])
15561 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15562 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15563 snprintf(buf, MSG_SIZ, "option %s", p);
15564 if(p = strstr(buf, ",")) *p = 0;
15565 if(q = strchr(buf, '=')) switch(opt->type) {
15567 for(n=0; n<opt->max; n++)
15568 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15571 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15575 opt->value = atoi(q+1);
15580 SendToProgram(buf, cps);
15586 FeatureDone (ChessProgramState *cps, int val)
15588 DelayedEventCallback cb = GetDelayedEvent();
15589 if ((cb == InitBackEnd3 && cps == &first) ||
15590 (cb == SettingsMenuIfReady && cps == &second) ||
15591 (cb == LoadEngine) ||
15592 (cb == TwoMachinesEventIfReady)) {
15593 CancelDelayedEvent();
15594 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15596 cps->initDone = val;
15599 /* Parse feature command from engine */
15601 ParseFeatures (char *args, ChessProgramState *cps)
15609 while (*p == ' ') p++;
15610 if (*p == NULLCHAR) return;
15612 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15613 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15614 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15615 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15616 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15617 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15618 if (BoolFeature(&p, "reuse", &val, cps)) {
15619 /* Engine can disable reuse, but can't enable it if user said no */
15620 if (!val) cps->reuse = FALSE;
15623 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15624 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15625 if (gameMode == TwoMachinesPlay) {
15626 DisplayTwoMachinesTitle();
15632 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15633 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15634 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15635 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15636 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15637 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15638 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15639 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15640 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15641 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15642 if (IntFeature(&p, "done", &val, cps)) {
15643 FeatureDone(cps, val);
15646 /* Added by Tord: */
15647 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15648 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15649 /* End of additions by Tord */
15651 /* [HGM] added features: */
15652 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15653 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15654 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15655 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15656 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15657 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15658 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15659 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15660 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15661 SendToProgram(buf, cps);
15664 if(cps->nrOptions >= MAX_OPTIONS) {
15666 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15667 DisplayError(buf, 0);
15671 /* End of additions by HGM */
15673 /* unknown feature: complain and skip */
15675 while (*q && *q != '=') q++;
15676 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15677 SendToProgram(buf, cps);
15683 while (*p && *p != '\"') p++;
15684 if (*p == '\"') p++;
15686 while (*p && *p != ' ') p++;
15694 PeriodicUpdatesEvent (int newState)
15696 if (newState == appData.periodicUpdates)
15699 appData.periodicUpdates=newState;
15701 /* Display type changes, so update it now */
15702 // DisplayAnalysis();
15704 /* Get the ball rolling again... */
15706 AnalysisPeriodicEvent(1);
15707 StartAnalysisClock();
15712 PonderNextMoveEvent (int newState)
15714 if (newState == appData.ponderNextMove) return;
15715 if (gameMode == EditPosition) EditPositionDone(TRUE);
15717 SendToProgram("hard\n", &first);
15718 if (gameMode == TwoMachinesPlay) {
15719 SendToProgram("hard\n", &second);
15722 SendToProgram("easy\n", &first);
15723 thinkOutput[0] = NULLCHAR;
15724 if (gameMode == TwoMachinesPlay) {
15725 SendToProgram("easy\n", &second);
15728 appData.ponderNextMove = newState;
15732 NewSettingEvent (int option, int *feature, char *command, int value)
15736 if (gameMode == EditPosition) EditPositionDone(TRUE);
15737 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15738 if(feature == NULL || *feature) SendToProgram(buf, &first);
15739 if (gameMode == TwoMachinesPlay) {
15740 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15745 ShowThinkingEvent ()
15746 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15748 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15749 int newState = appData.showThinking
15750 // [HGM] thinking: other features now need thinking output as well
15751 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15753 if (oldState == newState) return;
15754 oldState = newState;
15755 if (gameMode == EditPosition) EditPositionDone(TRUE);
15757 SendToProgram("post\n", &first);
15758 if (gameMode == TwoMachinesPlay) {
15759 SendToProgram("post\n", &second);
15762 SendToProgram("nopost\n", &first);
15763 thinkOutput[0] = NULLCHAR;
15764 if (gameMode == TwoMachinesPlay) {
15765 SendToProgram("nopost\n", &second);
15768 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15772 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15774 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15775 if (pr == NoProc) return;
15776 AskQuestion(title, question, replyPrefix, pr);
15780 TypeInEvent (char firstChar)
15782 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15783 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15784 gameMode == AnalyzeMode || gameMode == EditGame ||
15785 gameMode == EditPosition || gameMode == IcsExamining ||
15786 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15787 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15788 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15789 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15790 gameMode == Training) PopUpMoveDialog(firstChar);
15794 TypeInDoneEvent (char *move)
15797 int n, fromX, fromY, toX, toY;
15799 ChessMove moveType;
15802 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15803 EditPositionPasteFEN(move);
15806 // [HGM] movenum: allow move number to be typed in any mode
15807 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15811 // undocumented kludge: allow command-line option to be typed in!
15812 // (potentially fatal, and does not implement the effect of the option.)
15813 // should only be used for options that are values on which future decisions will be made,
15814 // and definitely not on options that would be used during initialization.
15815 if(strstr(move, "!!! -") == move) {
15816 ParseArgsFromString(move+4);
15820 if (gameMode != EditGame && currentMove != forwardMostMove &&
15821 gameMode != Training) {
15822 DisplayMoveError(_("Displayed move is not current"));
15824 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15825 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15826 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15827 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15828 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15829 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15831 DisplayMoveError(_("Could not parse move"));
15837 DisplayMove (int moveNumber)
15839 char message[MSG_SIZ];
15841 char cpThinkOutput[MSG_SIZ];
15843 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15845 if (moveNumber == forwardMostMove - 1 ||
15846 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15848 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15850 if (strchr(cpThinkOutput, '\n')) {
15851 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15854 *cpThinkOutput = NULLCHAR;
15857 /* [AS] Hide thinking from human user */
15858 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15859 *cpThinkOutput = NULLCHAR;
15860 if( thinkOutput[0] != NULLCHAR ) {
15863 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15864 cpThinkOutput[i] = '.';
15866 cpThinkOutput[i] = NULLCHAR;
15867 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15871 if (moveNumber == forwardMostMove - 1 &&
15872 gameInfo.resultDetails != NULL) {
15873 if (gameInfo.resultDetails[0] == NULLCHAR) {
15874 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15876 snprintf(res, MSG_SIZ, " {%s} %s",
15877 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15883 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15884 DisplayMessage(res, cpThinkOutput);
15886 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15887 WhiteOnMove(moveNumber) ? " " : ".. ",
15888 parseList[moveNumber], res);
15889 DisplayMessage(message, cpThinkOutput);
15894 DisplayComment (int moveNumber, char *text)
15896 char title[MSG_SIZ];
15898 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15899 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15901 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15902 WhiteOnMove(moveNumber) ? " " : ".. ",
15903 parseList[moveNumber]);
15905 if (text != NULL && (appData.autoDisplayComment || commentUp))
15906 CommentPopUp(title, text);
15909 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15910 * might be busy thinking or pondering. It can be omitted if your
15911 * gnuchess is configured to stop thinking immediately on any user
15912 * input. However, that gnuchess feature depends on the FIONREAD
15913 * ioctl, which does not work properly on some flavors of Unix.
15916 Attention (ChessProgramState *cps)
15919 if (!cps->useSigint) return;
15920 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15921 switch (gameMode) {
15922 case MachinePlaysWhite:
15923 case MachinePlaysBlack:
15924 case TwoMachinesPlay:
15925 case IcsPlayingWhite:
15926 case IcsPlayingBlack:
15929 /* Skip if we know it isn't thinking */
15930 if (!cps->maybeThinking) return;
15931 if (appData.debugMode)
15932 fprintf(debugFP, "Interrupting %s\n", cps->which);
15933 InterruptChildProcess(cps->pr);
15934 cps->maybeThinking = FALSE;
15939 #endif /*ATTENTION*/
15945 if (whiteTimeRemaining <= 0) {
15948 if (appData.icsActive) {
15949 if (appData.autoCallFlag &&
15950 gameMode == IcsPlayingBlack && !blackFlag) {
15951 SendToICS(ics_prefix);
15952 SendToICS("flag\n");
15956 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15958 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15959 if (appData.autoCallFlag) {
15960 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15967 if (blackTimeRemaining <= 0) {
15970 if (appData.icsActive) {
15971 if (appData.autoCallFlag &&
15972 gameMode == IcsPlayingWhite && !whiteFlag) {
15973 SendToICS(ics_prefix);
15974 SendToICS("flag\n");
15978 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15980 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15981 if (appData.autoCallFlag) {
15982 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15993 CheckTimeControl ()
15995 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15996 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15999 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16001 if ( !WhiteOnMove(forwardMostMove) ) {
16002 /* White made time control */
16003 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16004 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16005 /* [HGM] time odds: correct new time quota for time odds! */
16006 / WhitePlayer()->timeOdds;
16007 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16009 lastBlack -= blackTimeRemaining;
16010 /* Black made time control */
16011 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16012 / WhitePlayer()->other->timeOdds;
16013 lastWhite = whiteTimeRemaining;
16018 DisplayBothClocks ()
16020 int wom = gameMode == EditPosition ?
16021 !blackPlaysFirst : WhiteOnMove(currentMove);
16022 DisplayWhiteClock(whiteTimeRemaining, wom);
16023 DisplayBlackClock(blackTimeRemaining, !wom);
16027 /* Timekeeping seems to be a portability nightmare. I think everyone
16028 has ftime(), but I'm really not sure, so I'm including some ifdefs
16029 to use other calls if you don't. Clocks will be less accurate if
16030 you have neither ftime nor gettimeofday.
16033 /* VS 2008 requires the #include outside of the function */
16034 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16035 #include <sys/timeb.h>
16038 /* Get the current time as a TimeMark */
16040 GetTimeMark (TimeMark *tm)
16042 #if HAVE_GETTIMEOFDAY
16044 struct timeval timeVal;
16045 struct timezone timeZone;
16047 gettimeofday(&timeVal, &timeZone);
16048 tm->sec = (long) timeVal.tv_sec;
16049 tm->ms = (int) (timeVal.tv_usec / 1000L);
16051 #else /*!HAVE_GETTIMEOFDAY*/
16054 // include <sys/timeb.h> / moved to just above start of function
16055 struct timeb timeB;
16058 tm->sec = (long) timeB.time;
16059 tm->ms = (int) timeB.millitm;
16061 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16062 tm->sec = (long) time(NULL);
16068 /* Return the difference in milliseconds between two
16069 time marks. We assume the difference will fit in a long!
16072 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16074 return 1000L*(tm2->sec - tm1->sec) +
16075 (long) (tm2->ms - tm1->ms);
16080 * Code to manage the game clocks.
16082 * In tournament play, black starts the clock and then white makes a move.
16083 * We give the human user a slight advantage if he is playing white---the
16084 * clocks don't run until he makes his first move, so it takes zero time.
16085 * Also, we don't account for network lag, so we could get out of sync
16086 * with GNU Chess's clock -- but then, referees are always right.
16089 static TimeMark tickStartTM;
16090 static long intendedTickLength;
16093 NextTickLength (long timeRemaining)
16095 long nominalTickLength, nextTickLength;
16097 if (timeRemaining > 0L && timeRemaining <= 10000L)
16098 nominalTickLength = 100L;
16100 nominalTickLength = 1000L;
16101 nextTickLength = timeRemaining % nominalTickLength;
16102 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16104 return nextTickLength;
16107 /* Adjust clock one minute up or down */
16109 AdjustClock (Boolean which, int dir)
16111 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16112 if(which) blackTimeRemaining += 60000*dir;
16113 else whiteTimeRemaining += 60000*dir;
16114 DisplayBothClocks();
16115 adjustedClock = TRUE;
16118 /* Stop clocks and reset to a fresh time control */
16122 (void) StopClockTimer();
16123 if (appData.icsActive) {
16124 whiteTimeRemaining = blackTimeRemaining = 0;
16125 } else if (searchTime) {
16126 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16127 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16128 } else { /* [HGM] correct new time quote for time odds */
16129 whiteTC = blackTC = fullTimeControlString;
16130 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16131 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16133 if (whiteFlag || blackFlag) {
16135 whiteFlag = blackFlag = FALSE;
16137 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16138 DisplayBothClocks();
16139 adjustedClock = FALSE;
16142 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16144 /* Decrement running clock by amount of time that has passed */
16148 long timeRemaining;
16149 long lastTickLength, fudge;
16152 if (!appData.clockMode) return;
16153 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16157 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16159 /* Fudge if we woke up a little too soon */
16160 fudge = intendedTickLength - lastTickLength;
16161 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16163 if (WhiteOnMove(forwardMostMove)) {
16164 if(whiteNPS >= 0) lastTickLength = 0;
16165 timeRemaining = whiteTimeRemaining -= lastTickLength;
16166 if(timeRemaining < 0 && !appData.icsActive) {
16167 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16168 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16169 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16170 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16173 DisplayWhiteClock(whiteTimeRemaining - fudge,
16174 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16176 if(blackNPS >= 0) lastTickLength = 0;
16177 timeRemaining = blackTimeRemaining -= lastTickLength;
16178 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16179 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16181 blackStartMove = forwardMostMove;
16182 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16185 DisplayBlackClock(blackTimeRemaining - fudge,
16186 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16188 if (CheckFlags()) return;
16191 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16192 StartClockTimer(intendedTickLength);
16194 /* if the time remaining has fallen below the alarm threshold, sound the
16195 * alarm. if the alarm has sounded and (due to a takeback or time control
16196 * with increment) the time remaining has increased to a level above the
16197 * threshold, reset the alarm so it can sound again.
16200 if (appData.icsActive && appData.icsAlarm) {
16202 /* make sure we are dealing with the user's clock */
16203 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16204 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16207 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16208 alarmSounded = FALSE;
16209 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16211 alarmSounded = TRUE;
16217 /* A player has just moved, so stop the previously running
16218 clock and (if in clock mode) start the other one.
16219 We redisplay both clocks in case we're in ICS mode, because
16220 ICS gives us an update to both clocks after every move.
16221 Note that this routine is called *after* forwardMostMove
16222 is updated, so the last fractional tick must be subtracted
16223 from the color that is *not* on move now.
16226 SwitchClocks (int newMoveNr)
16228 long lastTickLength;
16230 int flagged = FALSE;
16234 if (StopClockTimer() && appData.clockMode) {
16235 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16236 if (!WhiteOnMove(forwardMostMove)) {
16237 if(blackNPS >= 0) lastTickLength = 0;
16238 blackTimeRemaining -= lastTickLength;
16239 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16240 // if(pvInfoList[forwardMostMove].time == -1)
16241 pvInfoList[forwardMostMove].time = // use GUI time
16242 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16244 if(whiteNPS >= 0) lastTickLength = 0;
16245 whiteTimeRemaining -= lastTickLength;
16246 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16247 // if(pvInfoList[forwardMostMove].time == -1)
16248 pvInfoList[forwardMostMove].time =
16249 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16251 flagged = CheckFlags();
16253 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16254 CheckTimeControl();
16256 if (flagged || !appData.clockMode) return;
16258 switch (gameMode) {
16259 case MachinePlaysBlack:
16260 case MachinePlaysWhite:
16261 case BeginningOfGame:
16262 if (pausing) return;
16266 case PlayFromGameFile:
16274 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16275 if(WhiteOnMove(forwardMostMove))
16276 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16277 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16281 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16282 whiteTimeRemaining : blackTimeRemaining);
16283 StartClockTimer(intendedTickLength);
16287 /* Stop both clocks */
16291 long lastTickLength;
16294 if (!StopClockTimer()) return;
16295 if (!appData.clockMode) return;
16299 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16300 if (WhiteOnMove(forwardMostMove)) {
16301 if(whiteNPS >= 0) lastTickLength = 0;
16302 whiteTimeRemaining -= lastTickLength;
16303 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16305 if(blackNPS >= 0) lastTickLength = 0;
16306 blackTimeRemaining -= lastTickLength;
16307 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16312 /* Start clock of player on move. Time may have been reset, so
16313 if clock is already running, stop and restart it. */
16317 (void) StopClockTimer(); /* in case it was running already */
16318 DisplayBothClocks();
16319 if (CheckFlags()) return;
16321 if (!appData.clockMode) return;
16322 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16324 GetTimeMark(&tickStartTM);
16325 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16326 whiteTimeRemaining : blackTimeRemaining);
16328 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16329 whiteNPS = blackNPS = -1;
16330 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16331 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16332 whiteNPS = first.nps;
16333 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16334 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16335 blackNPS = first.nps;
16336 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16337 whiteNPS = second.nps;
16338 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16339 blackNPS = second.nps;
16340 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16342 StartClockTimer(intendedTickLength);
16346 TimeString (long ms)
16348 long second, minute, hour, day;
16350 static char buf[32];
16352 if (ms > 0 && ms <= 9900) {
16353 /* convert milliseconds to tenths, rounding up */
16354 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16356 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16360 /* convert milliseconds to seconds, rounding up */
16361 /* use floating point to avoid strangeness of integer division
16362 with negative dividends on many machines */
16363 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16370 day = second / (60 * 60 * 24);
16371 second = second % (60 * 60 * 24);
16372 hour = second / (60 * 60);
16373 second = second % (60 * 60);
16374 minute = second / 60;
16375 second = second % 60;
16378 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16379 sign, day, hour, minute, second);
16381 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16383 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16390 * This is necessary because some C libraries aren't ANSI C compliant yet.
16393 StrStr (char *string, char *match)
16397 length = strlen(match);
16399 for (i = strlen(string) - length; i >= 0; i--, string++)
16400 if (!strncmp(match, string, length))
16407 StrCaseStr (char *string, char *match)
16411 length = strlen(match);
16413 for (i = strlen(string) - length; i >= 0; i--, string++) {
16414 for (j = 0; j < length; j++) {
16415 if (ToLower(match[j]) != ToLower(string[j]))
16418 if (j == length) return string;
16426 StrCaseCmp (char *s1, char *s2)
16431 c1 = ToLower(*s1++);
16432 c2 = ToLower(*s2++);
16433 if (c1 > c2) return 1;
16434 if (c1 < c2) return -1;
16435 if (c1 == NULLCHAR) return 0;
16443 return isupper(c) ? tolower(c) : c;
16450 return islower(c) ? toupper(c) : c;
16452 #endif /* !_amigados */
16459 if ((ret = (char *) malloc(strlen(s) + 1)))
16461 safeStrCpy(ret, s, strlen(s)+1);
16467 StrSavePtr (char *s, char **savePtr)
16472 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16473 safeStrCpy(*savePtr, s, strlen(s)+1);
16485 clock = time((time_t *)NULL);
16486 tm = localtime(&clock);
16487 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16488 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16489 return StrSave(buf);
16494 PositionToFEN (int move, char *overrideCastling)
16496 int i, j, fromX, fromY, toX, toY;
16503 whiteToPlay = (gameMode == EditPosition) ?
16504 !blackPlaysFirst : (move % 2 == 0);
16507 /* Piece placement data */
16508 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16509 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16511 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16512 if (boards[move][i][j] == EmptySquare) {
16514 } else { ChessSquare piece = boards[move][i][j];
16515 if (emptycount > 0) {
16516 if(emptycount<10) /* [HGM] can be >= 10 */
16517 *p++ = '0' + emptycount;
16518 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16521 if(PieceToChar(piece) == '+') {
16522 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16524 piece = (ChessSquare)(DEMOTED piece);
16526 *p++ = PieceToChar(piece);
16528 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16529 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16534 if (emptycount > 0) {
16535 if(emptycount<10) /* [HGM] can be >= 10 */
16536 *p++ = '0' + emptycount;
16537 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16544 /* [HGM] print Crazyhouse or Shogi holdings */
16545 if( gameInfo.holdingsWidth ) {
16546 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16548 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16549 piece = boards[move][i][BOARD_WIDTH-1];
16550 if( piece != EmptySquare )
16551 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16552 *p++ = PieceToChar(piece);
16554 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16555 piece = boards[move][BOARD_HEIGHT-i-1][0];
16556 if( piece != EmptySquare )
16557 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16558 *p++ = PieceToChar(piece);
16561 if( q == p ) *p++ = '-';
16567 *p++ = whiteToPlay ? 'w' : 'b';
16570 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16571 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16573 if(nrCastlingRights) {
16575 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16576 /* [HGM] write directly from rights */
16577 if(boards[move][CASTLING][2] != NoRights &&
16578 boards[move][CASTLING][0] != NoRights )
16579 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16580 if(boards[move][CASTLING][2] != NoRights &&
16581 boards[move][CASTLING][1] != NoRights )
16582 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16583 if(boards[move][CASTLING][5] != NoRights &&
16584 boards[move][CASTLING][3] != NoRights )
16585 *p++ = boards[move][CASTLING][3] + AAA;
16586 if(boards[move][CASTLING][5] != NoRights &&
16587 boards[move][CASTLING][4] != NoRights )
16588 *p++ = boards[move][CASTLING][4] + AAA;
16591 /* [HGM] write true castling rights */
16592 if( nrCastlingRights == 6 ) {
16593 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16594 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16595 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16596 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16597 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16598 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16599 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16600 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16603 if (q == p) *p++ = '-'; /* No castling rights */
16607 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16608 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16609 /* En passant target square */
16610 if (move > backwardMostMove) {
16611 fromX = moveList[move - 1][0] - AAA;
16612 fromY = moveList[move - 1][1] - ONE;
16613 toX = moveList[move - 1][2] - AAA;
16614 toY = moveList[move - 1][3] - ONE;
16615 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16616 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16617 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16619 /* 2-square pawn move just happened */
16621 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16625 } else if(move == backwardMostMove) {
16626 // [HGM] perhaps we should always do it like this, and forget the above?
16627 if((signed char)boards[move][EP_STATUS] >= 0) {
16628 *p++ = boards[move][EP_STATUS] + AAA;
16629 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16640 /* [HGM] find reversible plies */
16641 { int i = 0, j=move;
16643 if (appData.debugMode) { int k;
16644 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16645 for(k=backwardMostMove; k<=forwardMostMove; k++)
16646 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16650 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16651 if( j == backwardMostMove ) i += initialRulePlies;
16652 sprintf(p, "%d ", i);
16653 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16655 /* Fullmove number */
16656 sprintf(p, "%d", (move / 2) + 1);
16658 return StrSave(buf);
16662 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16671 /* [HGM] by default clear Crazyhouse holdings, if present */
16672 if(gameInfo.holdingsWidth) {
16673 for(i=0; i<BOARD_HEIGHT; i++) {
16674 board[i][0] = EmptySquare; /* black holdings */
16675 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16676 board[i][1] = (ChessSquare) 0; /* black counts */
16677 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16681 /* Piece placement data */
16682 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16685 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16686 if (*p == '/') p++;
16687 emptycount = gameInfo.boardWidth - j;
16688 while (emptycount--)
16689 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16691 #if(BOARD_FILES >= 10)
16692 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16693 p++; emptycount=10;
16694 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16695 while (emptycount--)
16696 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16698 } else if (isdigit(*p)) {
16699 emptycount = *p++ - '0';
16700 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16701 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16702 while (emptycount--)
16703 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16704 } else if (*p == '+' || isalpha(*p)) {
16705 if (j >= gameInfo.boardWidth) return FALSE;
16707 piece = CharToPiece(*++p);
16708 if(piece == EmptySquare) return FALSE; /* unknown piece */
16709 piece = (ChessSquare) (PROMOTED piece ); p++;
16710 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16711 } else piece = CharToPiece(*p++);
16713 if(piece==EmptySquare) return FALSE; /* unknown piece */
16714 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16715 piece = (ChessSquare) (PROMOTED piece);
16716 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16719 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16725 while (*p == '/' || *p == ' ') p++;
16727 /* [HGM] look for Crazyhouse holdings here */
16728 while(*p==' ') p++;
16729 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16731 if(*p == '-' ) p++; /* empty holdings */ else {
16732 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16733 /* if we would allow FEN reading to set board size, we would */
16734 /* have to add holdings and shift the board read so far here */
16735 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16737 if((int) piece >= (int) BlackPawn ) {
16738 i = (int)piece - (int)BlackPawn;
16739 i = PieceToNumber((ChessSquare)i);
16740 if( i >= gameInfo.holdingsSize ) return FALSE;
16741 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16742 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16744 i = (int)piece - (int)WhitePawn;
16745 i = PieceToNumber((ChessSquare)i);
16746 if( i >= gameInfo.holdingsSize ) return FALSE;
16747 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16748 board[i][BOARD_WIDTH-2]++; /* black holdings */
16755 while(*p == ' ') p++;
16759 if(appData.colorNickNames) {
16760 if( c == appData.colorNickNames[0] ) c = 'w'; else
16761 if( c == appData.colorNickNames[1] ) c = 'b';
16765 *blackPlaysFirst = FALSE;
16768 *blackPlaysFirst = TRUE;
16774 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16775 /* return the extra info in global variiables */
16777 /* set defaults in case FEN is incomplete */
16778 board[EP_STATUS] = EP_UNKNOWN;
16779 for(i=0; i<nrCastlingRights; i++ ) {
16780 board[CASTLING][i] =
16781 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16782 } /* assume possible unless obviously impossible */
16783 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16784 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16785 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16786 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16787 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16788 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16789 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16790 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16793 while(*p==' ') p++;
16794 if(nrCastlingRights) {
16795 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16796 /* castling indicator present, so default becomes no castlings */
16797 for(i=0; i<nrCastlingRights; i++ ) {
16798 board[CASTLING][i] = NoRights;
16801 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16802 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16803 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16804 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16805 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16807 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16808 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16809 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16811 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16812 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16813 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16814 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16815 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16816 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16819 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16820 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16821 board[CASTLING][2] = whiteKingFile;
16824 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16825 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16826 board[CASTLING][2] = whiteKingFile;
16829 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16830 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16831 board[CASTLING][5] = blackKingFile;
16834 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16835 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16836 board[CASTLING][5] = blackKingFile;
16839 default: /* FRC castlings */
16840 if(c >= 'a') { /* black rights */
16841 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16842 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16843 if(i == BOARD_RGHT) break;
16844 board[CASTLING][5] = i;
16846 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16847 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16849 board[CASTLING][3] = c;
16851 board[CASTLING][4] = c;
16852 } else { /* white rights */
16853 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16854 if(board[0][i] == WhiteKing) break;
16855 if(i == BOARD_RGHT) break;
16856 board[CASTLING][2] = i;
16857 c -= AAA - 'a' + 'A';
16858 if(board[0][c] >= WhiteKing) break;
16860 board[CASTLING][0] = c;
16862 board[CASTLING][1] = c;
16866 for(i=0; i<nrCastlingRights; i++)
16867 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16868 if (appData.debugMode) {
16869 fprintf(debugFP, "FEN castling rights:");
16870 for(i=0; i<nrCastlingRights; i++)
16871 fprintf(debugFP, " %d", board[CASTLING][i]);
16872 fprintf(debugFP, "\n");
16875 while(*p==' ') p++;
16878 /* read e.p. field in games that know e.p. capture */
16879 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16880 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16882 p++; board[EP_STATUS] = EP_NONE;
16884 char c = *p++ - AAA;
16886 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16887 if(*p >= '0' && *p <='9') p++;
16888 board[EP_STATUS] = c;
16893 if(sscanf(p, "%d", &i) == 1) {
16894 FENrulePlies = i; /* 50-move ply counter */
16895 /* (The move number is still ignored) */
16902 EditPositionPasteFEN (char *fen)
16905 Board initial_position;
16907 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16908 DisplayError(_("Bad FEN position in clipboard"), 0);
16911 int savedBlackPlaysFirst = blackPlaysFirst;
16912 EditPositionEvent();
16913 blackPlaysFirst = savedBlackPlaysFirst;
16914 CopyBoard(boards[0], initial_position);
16915 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16916 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16917 DisplayBothClocks();
16918 DrawPosition(FALSE, boards[currentMove]);
16923 static char cseq[12] = "\\ ";
16926 set_cont_sequence (char *new_seq)
16931 // handle bad attempts to set the sequence
16933 return 0; // acceptable error - no debug
16935 len = strlen(new_seq);
16936 ret = (len > 0) && (len < sizeof(cseq));
16938 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16939 else if (appData.debugMode)
16940 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16945 reformat a source message so words don't cross the width boundary. internal
16946 newlines are not removed. returns the wrapped size (no null character unless
16947 included in source message). If dest is NULL, only calculate the size required
16948 for the dest buffer. lp argument indicats line position upon entry, and it's
16949 passed back upon exit.
16952 wrap (char *dest, char *src, int count, int width, int *lp)
16954 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16956 cseq_len = strlen(cseq);
16957 old_line = line = *lp;
16958 ansi = len = clen = 0;
16960 for (i=0; i < count; i++)
16962 if (src[i] == '\033')
16965 // if we hit the width, back up
16966 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16968 // store i & len in case the word is too long
16969 old_i = i, old_len = len;
16971 // find the end of the last word
16972 while (i && src[i] != ' ' && src[i] != '\n')
16978 // word too long? restore i & len before splitting it
16979 if ((old_i-i+clen) >= width)
16986 if (i && src[i-1] == ' ')
16989 if (src[i] != ' ' && src[i] != '\n')
16996 // now append the newline and continuation sequence
17001 strncpy(dest+len, cseq, cseq_len);
17009 dest[len] = src[i];
17013 if (src[i] == '\n')
17018 if (dest && appData.debugMode)
17020 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17021 count, width, line, len, *lp);
17022 show_bytes(debugFP, src, count);
17023 fprintf(debugFP, "\ndest: ");
17024 show_bytes(debugFP, dest, len);
17025 fprintf(debugFP, "\n");
17027 *lp = dest ? line : old_line;
17032 // [HGM] vari: routines for shelving variations
17033 Boolean modeRestore = FALSE;
17036 PushInner (int firstMove, int lastMove)
17038 int i, j, nrMoves = lastMove - firstMove;
17040 // push current tail of game on stack
17041 savedResult[storedGames] = gameInfo.result;
17042 savedDetails[storedGames] = gameInfo.resultDetails;
17043 gameInfo.resultDetails = NULL;
17044 savedFirst[storedGames] = firstMove;
17045 savedLast [storedGames] = lastMove;
17046 savedFramePtr[storedGames] = framePtr;
17047 framePtr -= nrMoves; // reserve space for the boards
17048 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17049 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17050 for(j=0; j<MOVE_LEN; j++)
17051 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17052 for(j=0; j<2*MOVE_LEN; j++)
17053 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17054 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17055 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17056 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17057 pvInfoList[firstMove+i-1].depth = 0;
17058 commentList[framePtr+i] = commentList[firstMove+i];
17059 commentList[firstMove+i] = NULL;
17063 forwardMostMove = firstMove; // truncate game so we can start variation
17067 PushTail (int firstMove, int lastMove)
17069 if(appData.icsActive) { // only in local mode
17070 forwardMostMove = currentMove; // mimic old ICS behavior
17073 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17075 PushInner(firstMove, lastMove);
17076 if(storedGames == 1) GreyRevert(FALSE);
17077 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17081 PopInner (Boolean annotate)
17084 char buf[8000], moveBuf[20];
17086 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17087 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17088 nrMoves = savedLast[storedGames] - currentMove;
17091 if(!WhiteOnMove(currentMove))
17092 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17093 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17094 for(i=currentMove; i<forwardMostMove; i++) {
17096 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17097 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17098 strcat(buf, moveBuf);
17099 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17100 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17104 for(i=1; i<=nrMoves; i++) { // copy last variation back
17105 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17106 for(j=0; j<MOVE_LEN; j++)
17107 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17108 for(j=0; j<2*MOVE_LEN; j++)
17109 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17110 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17111 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17112 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17113 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17114 commentList[currentMove+i] = commentList[framePtr+i];
17115 commentList[framePtr+i] = NULL;
17117 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17118 framePtr = savedFramePtr[storedGames];
17119 gameInfo.result = savedResult[storedGames];
17120 if(gameInfo.resultDetails != NULL) {
17121 free(gameInfo.resultDetails);
17123 gameInfo.resultDetails = savedDetails[storedGames];
17124 forwardMostMove = currentMove + nrMoves;
17128 PopTail (Boolean annotate)
17130 if(appData.icsActive) return FALSE; // only in local mode
17131 if(!storedGames) return FALSE; // sanity
17132 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17134 PopInner(annotate);
17135 if(currentMove < forwardMostMove) ForwardEvent(); else
17136 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17138 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17144 { // remove all shelved variations
17146 for(i=0; i<storedGames; i++) {
17147 if(savedDetails[i])
17148 free(savedDetails[i]);
17149 savedDetails[i] = NULL;
17151 for(i=framePtr; i<MAX_MOVES; i++) {
17152 if(commentList[i]) free(commentList[i]);
17153 commentList[i] = NULL;
17155 framePtr = MAX_MOVES-1;
17160 LoadVariation (int index, char *text)
17161 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17162 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17163 int level = 0, move;
17165 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17166 // first find outermost bracketing variation
17167 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17168 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17169 if(*p == '{') wait = '}'; else
17170 if(*p == '[') wait = ']'; else
17171 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17172 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17174 if(*p == wait) wait = NULLCHAR; // closing ]} found
17177 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17178 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17179 end[1] = NULLCHAR; // clip off comment beyond variation
17180 ToNrEvent(currentMove-1);
17181 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17182 // kludge: use ParsePV() to append variation to game
17183 move = currentMove;
17184 ParsePV(start, TRUE, TRUE);
17185 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17186 ClearPremoveHighlights();
17188 ToNrEvent(currentMove+1);