2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy (char *dst, const char *src, size_t count)
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble (u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second, pairing;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
455 int have_sent_ICS_logon = 0;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
507 ChessSquare FIDEArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackKnight, BlackRook }
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackKing, BlackKnight, BlackRook }
521 ChessSquare KnightmateArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524 { BlackRook, BlackMan, BlackBishop, BlackQueen,
525 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackMan, BlackFerz,
553 BlackKing, BlackMan, BlackKnight, BlackRook }
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating (char *str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine (ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions (ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
738 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 InitEngine (ChessProgramState *cps, int n)
748 { // [HGM] all engine initialiation put in a function that does one engine
752 cps->which = engineNames[n];
753 cps->maybeThinking = FALSE;
757 cps->sendDrawOffers = 1;
759 cps->program = appData.chessProgram[n];
760 cps->host = appData.host[n];
761 cps->dir = appData.directory[n];
762 cps->initString = appData.engInitString[n];
763 cps->computerString = appData.computerString[n];
764 cps->useSigint = TRUE;
765 cps->useSigterm = TRUE;
766 cps->reuse = appData.reuse[n];
767 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
768 cps->useSetboard = FALSE;
770 cps->usePing = FALSE;
773 cps->usePlayother = FALSE;
774 cps->useColors = TRUE;
775 cps->useUsermove = FALSE;
776 cps->sendICS = FALSE;
777 cps->sendName = appData.icsActive;
778 cps->sdKludge = FALSE;
779 cps->stKludge = FALSE;
780 TidyProgramName(cps->program, cps->host, cps->tidy);
782 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783 cps->analysisSupport = 2; /* detect */
784 cps->analyzing = FALSE;
785 cps->initDone = FALSE;
787 /* New features added by Tord: */
788 cps->useFEN960 = FALSE;
789 cps->useOOCastle = TRUE;
790 /* End of new features added by Tord. */
791 cps->fenOverride = appData.fenOverride[n];
793 /* [HGM] time odds: set factor for each machine */
794 cps->timeOdds = appData.timeOdds[n];
796 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797 cps->accumulateTC = appData.accumulateTC[n];
798 cps->maxNrOfSessions = 1;
803 cps->supportsNPS = UNKNOWN;
804 cps->memSize = FALSE;
805 cps->maxCores = FALSE;
806 cps->egtFormats[0] = NULLCHAR;
809 cps->optionSettings = appData.engOptions[n];
811 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812 cps->isUCI = appData.isUCI[n]; /* [AS] */
813 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815 if (appData.protocolVersion[n] > PROTOVER
816 || appData.protocolVersion[n] < 1)
821 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822 appData.protocolVersion[n]);
823 if( (len >= MSG_SIZ) && appData.debugMode )
824 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826 DisplayFatalError(buf, 0, 2);
830 cps->protocolVersion = appData.protocolVersion[n];
833 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ParseFeatures(appData.featureDefaults, cps);
837 ChessProgramState *savCps;
843 if(WaitForEngine(savCps, LoadEngine)) return;
844 CommonEngineInit(); // recalculate time odds
845 if(gameInfo.variant != StringToVariant(appData.variant)) {
846 // we changed variant when loading the engine; this forces us to reset
847 Reset(TRUE, savCps != &first);
848 EditGameEvent(); // for consistency with other path, as Reset changes mode
850 InitChessProgram(savCps, FALSE);
851 SendToProgram("force\n", savCps);
852 DisplayMessage("", "");
853 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
860 ReplaceEngine (ChessProgramState *cps, int n)
864 appData.noChessProgram = FALSE;
865 appData.clockMode = TRUE;
868 if(n) return; // only startup first engine immediately; second can wait
869 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876 static char resetOptions[] =
877 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883 FloatToFront(char **list, char *engineLine)
885 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887 if(appData.recentEngines <= 0) return;
888 TidyProgramName(engineLine, "localhost", tidy+1);
889 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890 strncpy(buf+1, *list, MSG_SIZ-50);
891 if(p = strstr(buf, tidy)) { // tidy name appears in list
892 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893 while(*p++ = *++q); // squeeze out
895 strcat(tidy, buf+1); // put list behind tidy name
896 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898 ASSIGN(*list, tidy+1);
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904 Load (ChessProgramState *cps, int i)
906 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912 appData.firstProtocolVersion = PROTOVER;
913 ParseArgsFromString(buf);
915 ReplaceEngine(cps, i);
916 FloatToFront(&appData.recentEngineList, engineLine);
920 while(q = strchr(p, SLASH)) p = q+1;
921 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922 if(engineDir[0] != NULLCHAR) {
923 ASSIGN(appData.directory[i], engineDir);
924 } else if(p != engineName) { // derive directory from engine path, when not given
926 ASSIGN(appData.directory[i], engineName);
928 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929 } else { ASSIGN(appData.directory[i], "."); }
931 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932 snprintf(command, MSG_SIZ, "%s %s", p, params);
935 ASSIGN(appData.chessProgram[i], p);
936 appData.isUCI[i] = isUCI;
937 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938 appData.hasOwnBookUCI[i] = hasBook;
939 if(!nickName[0]) useNick = FALSE;
940 if(useNick) ASSIGN(appData.pgnName[i], nickName);
944 q = firstChessProgramNames;
945 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948 quote, p, quote, appData.directory[i],
949 useNick ? " -fn \"" : "",
950 useNick ? nickName : "",
952 v1 ? " -firstProtocolVersion 1" : "",
953 hasBook ? "" : " -fNoOwnBookUCI",
954 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955 storeVariant ? " -variant " : "",
956 storeVariant ? VariantName(gameInfo.variant) : "");
957 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959 if(insert != q) insert[-1] = NULLCHAR;
960 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962 FloatToFront(&appData.recentEngineList, buf);
964 ReplaceEngine(cps, i);
970 int matched, min, sec;
972 * Parse timeControl resource
974 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975 appData.movesPerSession)) {
977 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978 DisplayFatalError(buf, 0, 2);
982 * Parse searchTime resource
984 if (*appData.searchTime != NULLCHAR) {
985 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987 searchTime = min * 60;
988 } else if (matched == 2) {
989 searchTime = min * 60 + sec;
992 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993 DisplayFatalError(buf, 0, 2);
1002 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005 GetTimeMark(&programStartTime);
1006 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007 appData.seedBase = random() + (random()<<15);
1008 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010 ClearProgramStats();
1011 programStats.ok_to_send = 1;
1012 programStats.seen_stat = 0;
1015 * Initialize game list
1021 * Internet chess server status
1023 if (appData.icsActive) {
1024 appData.matchMode = FALSE;
1025 appData.matchGames = 0;
1027 appData.noChessProgram = !appData.zippyPlay;
1029 appData.zippyPlay = FALSE;
1030 appData.zippyTalk = FALSE;
1031 appData.noChessProgram = TRUE;
1033 if (*appData.icsHelper != NULLCHAR) {
1034 appData.useTelnet = TRUE;
1035 appData.telnetProgram = appData.icsHelper;
1038 appData.zippyTalk = appData.zippyPlay = FALSE;
1041 /* [AS] Initialize pv info list [HGM] and game state */
1045 for( i=0; i<=framePtr; i++ ) {
1046 pvInfoList[i].depth = -1;
1047 boards[i][EP_STATUS] = EP_NONE;
1048 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1054 /* [AS] Adjudication threshold */
1055 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057 InitEngine(&first, 0);
1058 InitEngine(&second, 1);
1061 pairing.which = "pairing"; // pairing engine
1062 pairing.pr = NoProc;
1064 pairing.program = appData.pairingEngine;
1065 pairing.host = "localhost";
1068 if (appData.icsActive) {
1069 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1070 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071 appData.clockMode = FALSE;
1072 first.sendTime = second.sendTime = 0;
1076 /* Override some settings from environment variables, for backward
1077 compatibility. Unfortunately it's not feasible to have the env
1078 vars just set defaults, at least in xboard. Ugh.
1080 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085 if (!appData.icsActive) {
1089 /* Check for variants that are supported only in ICS mode,
1090 or not at all. Some that are accepted here nevertheless
1091 have bugs; see comments below.
1093 VariantClass variant = StringToVariant(appData.variant);
1095 case VariantBughouse: /* need four players and two boards */
1096 case VariantKriegspiel: /* need to hide pieces and move details */
1097 /* case VariantFischeRandom: (Fabien: moved below) */
1098 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099 if( (len >= MSG_SIZ) && appData.debugMode )
1100 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102 DisplayFatalError(buf, 0, 2);
1105 case VariantUnknown:
1106 case VariantLoadable:
1116 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117 if( (len >= MSG_SIZ) && appData.debugMode )
1118 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120 DisplayFatalError(buf, 0, 2);
1123 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1124 case VariantFairy: /* [HGM] TestLegality definitely off! */
1125 case VariantGothic: /* [HGM] should work */
1126 case VariantCapablanca: /* [HGM] should work */
1127 case VariantCourier: /* [HGM] initial forced moves not implemented */
1128 case VariantShogi: /* [HGM] could still mate with pawn drop */
1129 case VariantKnightmate: /* [HGM] should work */
1130 case VariantCylinder: /* [HGM] untested */
1131 case VariantFalcon: /* [HGM] untested */
1132 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133 offboard interposition not understood */
1134 case VariantNormal: /* definitely works! */
1135 case VariantWildCastle: /* pieces not automatically shuffled */
1136 case VariantNoCastle: /* pieces not automatically shuffled */
1137 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138 case VariantLosers: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantSuicide: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantGiveaway: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantTwoKings: /* should work */
1145 case VariantAtomic: /* should work except for win condition */
1146 case Variant3Check: /* should work except for win condition */
1147 case VariantShatranj: /* should work except for all win conditions */
1148 case VariantMakruk: /* should work except for draw countdown */
1149 case VariantBerolina: /* might work if TestLegality is off */
1150 case VariantCapaRandom: /* should work */
1151 case VariantJanus: /* should work */
1152 case VariantSuper: /* experimental */
1153 case VariantGreat: /* experimental, requires legality testing to be off */
1154 case VariantSChess: /* S-Chess, should work */
1155 case VariantGrand: /* should work */
1156 case VariantSpartan: /* should work */
1164 NextIntegerFromString (char ** str, long * value)
1169 while( *s == ' ' || *s == '\t' ) {
1175 if( *s >= '0' && *s <= '9' ) {
1176 while( *s >= '0' && *s <= '9' ) {
1177 *value = *value * 10 + (*s - '0');
1190 NextTimeControlFromString (char ** str, long * value)
1193 int result = NextIntegerFromString( str, &temp );
1196 *value = temp * 60; /* Minutes */
1197 if( **str == ':' ) {
1199 result = NextIntegerFromString( str, &temp );
1200 *value += temp; /* Seconds */
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210 int result = -1, type = 0; long temp, temp2;
1212 if(**str != ':') return -1; // old params remain in force!
1214 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215 if( NextIntegerFromString( str, &temp ) ) return -1;
1216 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219 /* time only: incremental or sudden-death time control */
1220 if(**str == '+') { /* increment follows; read it */
1222 if(**str == '!') type = *(*str)++; // Bronstein TC
1223 if(result = NextIntegerFromString( str, &temp2)) return -1;
1224 *inc = temp2 * 1000;
1225 if(**str == '.') { // read fraction of increment
1226 char *start = ++(*str);
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229 while(start++ < *str) temp2 /= 10;
1233 *moves = 0; *tc = temp * 1000; *incType = type;
1237 (*str)++; /* classical time control */
1238 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 { /* [HGM] get time to add from the multi-session time-control string */
1252 int incType, moves=1; /* kludge to force reading of first session */
1253 long time, increment;
1256 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260 if(movenr == -1) return time; /* last move before new session */
1261 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263 if(!moves) return increment; /* current session is incremental */
1264 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265 } while(movenr >= -1); /* try again for next session */
1267 return 0; // no new time quota on this move
1271 ParseTimeControl (char *tc, float ti, int mps)
1275 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1284 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291 snprintf(buf, MSG_SIZ, ":%s", mytc);
1293 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300 /* Parse second time control */
1303 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1311 timeControl_2 = tc2 * 1000;
1321 timeControl = tc1 * 1000;
1324 timeIncrement = ti * 1000; /* convert to ms */
1325 movesPerSession = 0;
1328 movesPerSession = mps;
1336 if (appData.debugMode) {
1337 fprintf(debugFP, "%s\n", programVersion);
1339 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341 set_cont_sequence(appData.wrapContSeq);
1342 if (appData.matchGames > 0) {
1343 appData.matchMode = TRUE;
1344 } else if (appData.matchMode) {
1345 appData.matchGames = 1;
1347 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348 appData.matchGames = appData.sameColorGames;
1349 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354 if (appData.noChessProgram || first.protocolVersion == 1) {
1357 /* kludge: allow timeout for initial "feature" commands */
1359 DisplayMessage("", _("Starting chess program"));
1360 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365 CalculateIndex (int index, int gameNr)
1366 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368 if(index > 0) return index; // fixed nmber
1369 if(index == 0) return 1;
1370 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376 LoadGameOrPosition (int gameNr)
1377 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378 if (*appData.loadGameFile != NULLCHAR) {
1379 if (!LoadGameFromFile(appData.loadGameFile,
1380 CalculateIndex(appData.loadGameIndex, gameNr),
1381 appData.loadGameFile, FALSE)) {
1382 DisplayFatalError(_("Bad game file"), 0, 1);
1385 } else if (*appData.loadPositionFile != NULLCHAR) {
1386 if (!LoadPositionFromFile(appData.loadPositionFile,
1387 CalculateIndex(appData.loadPositionIndex, gameNr),
1388 appData.loadPositionFile)) {
1389 DisplayFatalError(_("Bad position file"), 0, 1);
1397 ReserveGame (int gameNr, char resChar)
1399 FILE *tf = fopen(appData.tourneyFile, "r+");
1400 char *p, *q, c, buf[MSG_SIZ];
1401 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402 safeStrCpy(buf, lastMsg, MSG_SIZ);
1403 DisplayMessage(_("Pick new game"), "");
1404 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405 ParseArgsFromFile(tf);
1406 p = q = appData.results;
1407 if(appData.debugMode) {
1408 char *r = appData.participants;
1409 fprintf(debugFP, "results = '%s'\n", p);
1410 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411 fprintf(debugFP, "\n");
1413 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416 safeStrCpy(q, p, strlen(p) + 2);
1417 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423 fseek(tf, -(strlen(p)+4), SEEK_END);
1425 if(c != '"') // depending on DOS or Unix line endings we can be one off
1426 fseek(tf, -(strlen(p)+2), SEEK_END);
1427 else fseek(tf, -(strlen(p)+3), SEEK_END);
1428 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429 DisplayMessage(buf, "");
1430 free(p); appData.results = q;
1431 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433 int round = appData.defaultMatchGames * appData.tourneyType;
1434 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1435 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436 UnloadEngine(&first); // next game belongs to other pairing;
1437 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1443 MatchEvent (int mode)
1444 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446 if(matchMode) { // already in match mode: switch it off
1448 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451 // if(gameMode != BeginningOfGame) {
1452 // DisplayError(_("You can only start a match from the initial position."), 0);
1456 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457 /* Set up machine vs. machine match */
1459 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460 if(appData.tourneyFile[0]) {
1462 if(nextGame > appData.matchGames) {
1464 if(strchr(appData.results, '*') == NULL) {
1466 appData.tourneyCycles++;
1467 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469 NextTourneyGame(-1, &dummy);
1471 if(nextGame <= appData.matchGames) {
1472 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474 ScheduleDelayedEvent(NextMatchGame, 10000);
1479 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480 DisplayError(buf, 0);
1481 appData.tourneyFile[0] = 0;
1485 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1486 DisplayFatalError(_("Can't have a match with no chess programs"),
1491 matchGame = roundNr = 1;
1492 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499 InitBackEnd3 P((void))
1501 GameMode initialMode;
1505 InitChessProgram(&first, startedFromSetupPosition);
1507 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1508 free(programVersion);
1509 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514 if (appData.icsActive) {
1516 /* [DM] Make a console window if needed [HGM] merged ifs */
1522 if (*appData.icsCommPort != NULLCHAR)
1523 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524 appData.icsCommPort);
1526 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527 appData.icsHost, appData.icsPort);
1529 if( (len >= MSG_SIZ) && appData.debugMode )
1530 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532 DisplayFatalError(buf, err, 1);
1537 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542 } else if (appData.noChessProgram) {
1548 if (*appData.cmailGameName != NULLCHAR) {
1550 OpenLoopback(&cmailPR);
1552 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1556 DisplayMessage("", "");
1557 if (StrCaseCmp(appData.initialMode, "") == 0) {
1558 initialMode = BeginningOfGame;
1559 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566 initialMode = TwoMachinesPlay;
1567 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568 initialMode = AnalyzeFile;
1569 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570 initialMode = AnalyzeMode;
1571 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572 initialMode = MachinePlaysWhite;
1573 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574 initialMode = MachinePlaysBlack;
1575 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576 initialMode = EditGame;
1577 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578 initialMode = EditPosition;
1579 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580 initialMode = Training;
1582 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, 0, 2);
1590 if (appData.matchMode) {
1591 if(appData.tourneyFile[0]) { // start tourney from command line
1593 if(f = fopen(appData.tourneyFile, "r")) {
1594 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596 appData.clockMode = TRUE;
1598 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601 } else if (*appData.cmailGameName != NULLCHAR) {
1602 /* Set up cmail mode */
1603 ReloadCmailMsgEvent(TRUE);
1605 /* Set up other modes */
1606 if (initialMode == AnalyzeFile) {
1607 if (*appData.loadGameFile == NULLCHAR) {
1608 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1612 if (*appData.loadGameFile != NULLCHAR) {
1613 (void) LoadGameFromFile(appData.loadGameFile,
1614 appData.loadGameIndex,
1615 appData.loadGameFile, TRUE);
1616 } else if (*appData.loadPositionFile != NULLCHAR) {
1617 (void) LoadPositionFromFile(appData.loadPositionFile,
1618 appData.loadPositionIndex,
1619 appData.loadPositionFile);
1620 /* [HGM] try to make self-starting even after FEN load */
1621 /* to allow automatic setup of fairy variants with wtm */
1622 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623 gameMode = BeginningOfGame;
1624 setboardSpoiledMachineBlack = 1;
1626 /* [HGM] loadPos: make that every new game uses the setup */
1627 /* from file as long as we do not switch variant */
1628 if(!blackPlaysFirst) {
1629 startedFromPositionFile = TRUE;
1630 CopyBoard(filePosition, boards[0]);
1633 if (initialMode == AnalyzeMode) {
1634 if (appData.noChessProgram) {
1635 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638 if (appData.icsActive) {
1639 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1643 } else if (initialMode == AnalyzeFile) {
1644 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645 ShowThinkingEvent();
1647 AnalysisPeriodicEvent(1);
1648 } else if (initialMode == MachinePlaysWhite) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1654 if (appData.icsActive) {
1655 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1659 MachineWhiteEvent();
1660 } else if (initialMode == MachinePlaysBlack) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1666 if (appData.icsActive) {
1667 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1671 MachineBlackEvent();
1672 } else if (initialMode == TwoMachinesPlay) {
1673 if (appData.noChessProgram) {
1674 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1678 if (appData.icsActive) {
1679 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684 } else if (initialMode == EditGame) {
1686 } else if (initialMode == EditPosition) {
1687 EditPositionEvent();
1688 } else if (initialMode == Training) {
1689 if (*appData.loadGameFile == NULLCHAR) {
1690 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 DisplayBook(current+1);
1703 MoveHistorySet( movelist, first, last, current, pvInfoList );
1705 EvalGraphSet( first, last, current, pvInfoList );
1707 MakeEngineOutputTitle();
1711 * Establish will establish a contact to a remote host.port.
1712 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713 * used to talk to the host.
1714 * Returns 0 if okay, error code if not.
1721 if (*appData.icsCommPort != NULLCHAR) {
1722 /* Talk to the host through a serial comm port */
1723 return OpenCommPort(appData.icsCommPort, &icsPR);
1725 } else if (*appData.gateway != NULLCHAR) {
1726 if (*appData.remoteShell == NULLCHAR) {
1727 /* Use the rcmd protocol to run telnet program on a gateway host */
1728 snprintf(buf, sizeof(buf), "%s %s %s",
1729 appData.telnetProgram, appData.icsHost, appData.icsPort);
1730 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733 /* Use the rsh program to run telnet program on a gateway host */
1734 if (*appData.remoteUser == NULLCHAR) {
1735 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736 appData.gateway, appData.telnetProgram,
1737 appData.icsHost, appData.icsPort);
1739 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740 appData.remoteShell, appData.gateway,
1741 appData.remoteUser, appData.telnetProgram,
1742 appData.icsHost, appData.icsPort);
1744 return StartChildProcess(buf, "", &icsPR);
1747 } else if (appData.useTelnet) {
1748 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751 /* TCP socket interface differs somewhat between
1752 Unix and NT; handle details in the front end.
1754 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759 EscapeExpand (char *p, char *q)
1760 { // [HGM] initstring: routine to shape up string arguments
1761 while(*p++ = *q++) if(p[-1] == '\\')
1763 case 'n': p[-1] = '\n'; break;
1764 case 'r': p[-1] = '\r'; break;
1765 case 't': p[-1] = '\t'; break;
1766 case '\\': p[-1] = '\\'; break;
1767 case 0: *p = 0; return;
1768 default: p[-1] = q[-1]; break;
1773 show_bytes (FILE *fp, char *buf, int count)
1776 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777 fprintf(fp, "\\%03o", *buf & 0xff);
1786 /* Returns an errno value */
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 char buf[8192], *p, *q, *buflim;
1791 int left, newcount, outcount;
1793 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794 *appData.gateway != NULLCHAR) {
1795 if (appData.debugMode) {
1796 fprintf(debugFP, ">ICS: ");
1797 show_bytes(debugFP, message, count);
1798 fprintf(debugFP, "\n");
1800 return OutputToProcess(pr, message, count, outError);
1803 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, buf, newcount);
1813 fprintf(debugFP, "\n");
1815 outcount = OutputToProcess(pr, buf, newcount, outError);
1816 if (outcount < newcount) return -1; /* to be sure */
1823 } else if (((unsigned char) *p) == TN_IAC) {
1824 *q++ = (char) TN_IAC;
1831 if (appData.debugMode) {
1832 fprintf(debugFP, ">ICS: ");
1833 show_bytes(debugFP, buf, newcount);
1834 fprintf(debugFP, "\n");
1836 outcount = OutputToProcess(pr, buf, newcount, outError);
1837 if (outcount < newcount) return -1; /* to be sure */
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 int outError, outCount;
1845 static int gotEof = 0;
1847 /* Pass data read from player on to ICS */
1850 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851 if (outCount < count) {
1852 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854 } else if (count < 0) {
1855 RemoveInputSource(isr);
1856 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857 } else if (gotEof++ > 0) {
1858 RemoveInputSource(isr);
1859 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1865 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868 SendToICS("date\n");
1869 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1872 /* added routine for printf style output to ics */
1874 ics_printf (char *format, ...)
1876 char buffer[MSG_SIZ];
1879 va_start(args, format);
1880 vsnprintf(buffer, sizeof(buffer), format, args);
1881 buffer[sizeof(buffer)-1] = '\0';
1889 int count, outCount, outError;
1891 if (icsPR == NoProc) return;
1894 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895 if (outCount < count) {
1896 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 /* This is used for sending logon scripts to the ICS. Sending
1901 without a delay causes problems when using timestamp on ICC
1902 (at least on my machine). */
1904 SendToICSDelayed (char *s, long msdelay)
1906 int count, outCount, outError;
1908 if (icsPR == NoProc) return;
1911 if (appData.debugMode) {
1912 fprintf(debugFP, ">ICS: ");
1913 show_bytes(debugFP, s, count);
1914 fprintf(debugFP, "\n");
1916 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918 if (outCount < count) {
1919 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1924 /* Remove all highlighting escape sequences in s
1925 Also deletes any suffix starting with '('
1928 StripHighlightAndTitle (char *s)
1930 static char retbuf[MSG_SIZ];
1933 while (*s != NULLCHAR) {
1934 while (*s == '\033') {
1935 while (*s != NULLCHAR && !isalpha(*s)) s++;
1936 if (*s != NULLCHAR) s++;
1938 while (*s != NULLCHAR && *s != '\033') {
1939 if (*s == '(' || *s == '[') {
1950 /* Remove all highlighting escape sequences in s */
1952 StripHighlight (char *s)
1954 static char retbuf[MSG_SIZ];
1957 while (*s != NULLCHAR) {
1958 while (*s == '\033') {
1959 while (*s != NULLCHAR && !isalpha(*s)) s++;
1960 if (*s != NULLCHAR) s++;
1962 while (*s != NULLCHAR && *s != '\033') {
1970 char *variantNames[] = VARIANT_NAMES;
1972 VariantName (VariantClass v)
1974 return variantNames[v];
1978 /* Identify a variant from the strings the chess servers use or the
1979 PGN Variant tag names we use. */
1981 StringToVariant (char *e)
1985 VariantClass v = VariantNormal;
1986 int i, found = FALSE;
1992 /* [HGM] skip over optional board-size prefixes */
1993 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995 while( *e++ != '_');
1998 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2002 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003 if (StrCaseStr(e, variantNames[i])) {
2004 v = (VariantClass) i;
2011 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012 || StrCaseStr(e, "wild/fr")
2013 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014 v = VariantFischeRandom;
2015 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016 (i = 1, p = StrCaseStr(e, "w"))) {
2018 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2025 case 0: /* FICS only, actually */
2027 /* Castling legal even if K starts on d-file */
2028 v = VariantWildCastle;
2033 /* Castling illegal even if K & R happen to start in
2034 normal positions. */
2035 v = VariantNoCastle;
2048 /* Castling legal iff K & R start in normal positions */
2054 /* Special wilds for position setup; unclear what to do here */
2055 v = VariantLoadable;
2058 /* Bizarre ICC game */
2059 v = VariantTwoKings;
2062 v = VariantKriegspiel;
2068 v = VariantFischeRandom;
2071 v = VariantCrazyhouse;
2074 v = VariantBughouse;
2080 /* Not quite the same as FICS suicide! */
2081 v = VariantGiveaway;
2087 v = VariantShatranj;
2090 /* Temporary names for future ICC types. The name *will* change in
2091 the next xboard/WinBoard release after ICC defines it. */
2129 v = VariantCapablanca;
2132 v = VariantKnightmate;
2138 v = VariantCylinder;
2144 v = VariantCapaRandom;
2147 v = VariantBerolina;
2159 /* Found "wild" or "w" in the string but no number;
2160 must assume it's normal chess. */
2164 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165 if( (len >= MSG_SIZ) && appData.debugMode )
2166 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168 DisplayError(buf, 0);
2174 if (appData.debugMode) {
2175 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176 e, wnum, VariantName(v));
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185 advance *index beyond it, and set leftover_start to the new value of
2186 *index; else return FALSE. If pattern contains the character '*', it
2187 matches any sequence of characters not containing '\r', '\n', or the
2188 character following the '*' (if any), and the matched sequence(s) are
2189 copied into star_match.
2192 looking_at ( char *buf, int *index, char *pattern)
2194 char *bufp = &buf[*index], *patternp = pattern;
2196 char *matchp = star_match[0];
2199 if (*patternp == NULLCHAR) {
2200 *index = leftover_start = bufp - buf;
2204 if (*bufp == NULLCHAR) return FALSE;
2205 if (*patternp == '*') {
2206 if (*bufp == *(patternp + 1)) {
2208 matchp = star_match[++star_count];
2212 } else if (*bufp == '\n' || *bufp == '\r') {
2214 if (*patternp == NULLCHAR)
2219 *matchp++ = *bufp++;
2223 if (*patternp != *bufp) return FALSE;
2230 SendToPlayer (char *data, int length)
2232 int error, outCount;
2233 outCount = OutputToProcess(NoProc, data, length, &error);
2234 if (outCount < length) {
2235 DisplayFatalError(_("Error writing to display"), error, 1);
2240 PackHolding (char packed[], char *holding)
2250 switch (runlength) {
2261 sprintf(q, "%d", runlength);
2273 /* Telnet protocol requests from the front end */
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2277 unsigned char msg[3];
2278 int outCount, outError;
2280 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282 if (appData.debugMode) {
2283 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2299 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2308 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2311 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2316 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2325 if (!appData.icsActive) return;
2326 TelnetRequest(TN_DO, TN_ECHO);
2332 if (!appData.icsActive) return;
2333 TelnetRequest(TN_DONT, TN_ECHO);
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 /* put the holdings sent to us by the server on the board holdings area */
2340 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2344 if(gameInfo.holdingsWidth < 2) return;
2345 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346 return; // prevent overwriting by pre-board holdings
2348 if( (int)lowestPiece >= BlackPawn ) {
2351 holdingsStartRow = BOARD_HEIGHT-1;
2354 holdingsColumn = BOARD_WIDTH-1;
2355 countsColumn = BOARD_WIDTH-2;
2356 holdingsStartRow = 0;
2360 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361 board[i][holdingsColumn] = EmptySquare;
2362 board[i][countsColumn] = (ChessSquare) 0;
2364 while( (p=*holdings++) != NULLCHAR ) {
2365 piece = CharToPiece( ToUpper(p) );
2366 if(piece == EmptySquare) continue;
2367 /*j = (int) piece - (int) WhitePawn;*/
2368 j = PieceToNumber(piece);
2369 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370 if(j < 0) continue; /* should not happen */
2371 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373 board[holdingsStartRow+j*direction][countsColumn]++;
2379 VariantSwitch (Board board, VariantClass newVariant)
2381 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382 static Board oldBoard;
2384 startedFromPositionFile = FALSE;
2385 if(gameInfo.variant == newVariant) return;
2387 /* [HGM] This routine is called each time an assignment is made to
2388 * gameInfo.variant during a game, to make sure the board sizes
2389 * are set to match the new variant. If that means adding or deleting
2390 * holdings, we shift the playing board accordingly
2391 * This kludge is needed because in ICS observe mode, we get boards
2392 * of an ongoing game without knowing the variant, and learn about the
2393 * latter only later. This can be because of the move list we requested,
2394 * in which case the game history is refilled from the beginning anyway,
2395 * but also when receiving holdings of a crazyhouse game. In the latter
2396 * case we want to add those holdings to the already received position.
2400 if (appData.debugMode) {
2401 fprintf(debugFP, "Switch board from %s to %s\n",
2402 VariantName(gameInfo.variant), VariantName(newVariant));
2403 setbuf(debugFP, NULL);
2405 shuffleOpenings = 0; /* [HGM] shuffle */
2406 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2410 newWidth = 9; newHeight = 9;
2411 gameInfo.holdingsSize = 7;
2412 case VariantBughouse:
2413 case VariantCrazyhouse:
2414 newHoldingsWidth = 2; break;
2418 newHoldingsWidth = 2;
2419 gameInfo.holdingsSize = 8;
2422 case VariantCapablanca:
2423 case VariantCapaRandom:
2426 newHoldingsWidth = gameInfo.holdingsSize = 0;
2429 if(newWidth != gameInfo.boardWidth ||
2430 newHeight != gameInfo.boardHeight ||
2431 newHoldingsWidth != gameInfo.holdingsWidth ) {
2433 /* shift position to new playing area, if needed */
2434 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435 for(i=0; i<BOARD_HEIGHT; i++)
2436 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439 for(i=0; i<newHeight; i++) {
2440 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444 for(i=0; i<BOARD_HEIGHT; i++)
2445 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2449 gameInfo.boardWidth = newWidth;
2450 gameInfo.boardHeight = newHeight;
2451 gameInfo.holdingsWidth = newHoldingsWidth;
2452 gameInfo.variant = newVariant;
2453 InitDrawingSizes(-2, 0);
2454 } else gameInfo.variant = newVariant;
2455 CopyBoard(oldBoard, board); // remember correctly formatted board
2456 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2457 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 static int loggedOn = FALSE;
2462 /*-- Game start info cache: --*/
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\ ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2492 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494 if(r < minRating+100 && r >=0 ) r = minRating+100;
2495 if(r > maxRating) r = maxRating;
2496 if(tc < 1.) tc = 1.;
2497 if(tc > 95.) tc = 95.;
2498 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499 y = ((double)r - minRating)/(maxRating - minRating)
2500 * (h-vMargin-squareSize/8-1) + vMargin;
2501 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502 if(strstr(seekAdList[i], " u ")) color = 1;
2503 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504 !strstr(seekAdList[i], "bullet") &&
2505 !strstr(seekAdList[i], "blitz") &&
2506 !strstr(seekAdList[i], "standard") ) color = 2;
2507 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2512 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2514 char buf[MSG_SIZ], *ext = "";
2515 VariantClass v = StringToVariant(type);
2516 if(strstr(type, "wild")) {
2517 ext = type + 4; // append wild number
2518 if(v == VariantFischeRandom) type = "chess960"; else
2519 if(v == VariantLoadable) type = "setup"; else
2520 type = VariantName(v);
2522 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528 seekNrList[nrOfSeekAds] = nr;
2529 zList[nrOfSeekAds] = 0;
2530 seekAdList[nrOfSeekAds++] = StrSave(buf);
2531 if(plot) PlotSeekAd(nrOfSeekAds-1);
2536 EraseSeekDot (int i)
2538 int x = xList[i], y = yList[i], d=squareSize/4, k;
2539 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541 // now replot every dot that overlapped
2542 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543 int xx = xList[k], yy = yList[k];
2544 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545 DrawSeekDot(xx, yy, colorList[k]);
2550 RemoveSeekAd (int nr)
2553 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555 if(seekAdList[i]) free(seekAdList[i]);
2556 seekAdList[i] = seekAdList[--nrOfSeekAds];
2557 seekNrList[i] = seekNrList[nrOfSeekAds];
2558 ratingList[i] = ratingList[nrOfSeekAds];
2559 colorList[i] = colorList[nrOfSeekAds];
2560 tcList[i] = tcList[nrOfSeekAds];
2561 xList[i] = xList[nrOfSeekAds];
2562 yList[i] = yList[nrOfSeekAds];
2563 zList[i] = zList[nrOfSeekAds];
2564 seekAdList[nrOfSeekAds] = NULL;
2570 MatchSoughtLine (char *line)
2572 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573 int nr, base, inc, u=0; char dummy;
2575 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2580 // match: compact and save the line
2581 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2591 if(!seekGraphUp) return FALSE;
2592 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2595 DrawSeekBackground(0, 0, w, h);
2596 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604 snprintf(buf, MSG_SIZ, "%d", i);
2605 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609 for(i=1; i<100; i+=(i<10?1:5)) {
2610 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614 snprintf(buf, MSG_SIZ, "%d", i);
2615 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 static int lastDown = 0, displayed = 0, lastSecond;
2626 if(y < 0) return FALSE;
2627 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629 if(!seekGraphUp) return FALSE;
2630 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631 DrawPosition(TRUE, NULL);
2634 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635 if(click == Release || moving) return FALSE;
2637 soughtPending = TRUE;
2638 SendToICS(ics_prefix);
2639 SendToICS("sought\n"); // should this be "sought all"?
2640 } else { // issue challenge based on clicked ad
2641 int dist = 10000; int i, closest = 0, second = 0;
2642 for(i=0; i<nrOfSeekAds; i++) {
2643 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2644 if(d < dist) { dist = d; closest = i; }
2645 second += (d - zList[i] < 120); // count in-range ads
2646 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2650 second = (second > 1);
2651 if(displayed != closest || second != lastSecond) {
2652 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653 lastSecond = second; displayed = closest;
2655 if(click == Press) {
2656 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659 } // on press 'hit', only show info
2660 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662 SendToICS(ics_prefix);
2664 return TRUE; // let incoming board of started game pop down the graph
2665 } else if(click == Release) { // release 'miss' is ignored
2666 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667 if(moving == 2) { // right up-click
2668 nrOfSeekAds = 0; // refresh graph
2669 soughtPending = TRUE;
2670 SendToICS(ics_prefix);
2671 SendToICS("sought\n"); // should this be "sought all"?
2674 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675 // press miss or release hit 'pop down' seek graph
2676 seekGraphUp = FALSE;
2677 DrawPosition(TRUE, NULL);
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2695 static int started = STARTED_NONE;
2696 static char parse[20000];
2697 static int parse_pos = 0;
2698 static char buf[BUF_SIZE + 1];
2699 static int firstTime = TRUE, intfSet = FALSE;
2700 static ColorClass prevColor = ColorNormal;
2701 static int savingComment = FALSE;
2702 static int cmatch = 0; // continuation sequence match
2709 int backup; /* [DM] For zippy color lines */
2711 char talker[MSG_SIZ]; // [HGM] chat
2714 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716 if (appData.debugMode) {
2718 fprintf(debugFP, "<ICS: ");
2719 show_bytes(debugFP, data, count);
2720 fprintf(debugFP, "\n");
2724 if (appData.debugMode) { int f = forwardMostMove;
2725 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730 /* If last read ended with a partial line that we couldn't parse,
2731 prepend it to the new read and try again. */
2732 if (leftover_len > 0) {
2733 for (i=0; i<leftover_len; i++)
2734 buf[i] = buf[leftover_start + i];
2737 /* copy new characters into the buffer */
2738 bp = buf + leftover_len;
2739 buf_len=leftover_len;
2740 for (i=0; i<count; i++)
2743 if (data[i] == '\r')
2746 // join lines split by ICS?
2747 if (!appData.noJoin)
2750 Joining just consists of finding matches against the
2751 continuation sequence, and discarding that sequence
2752 if found instead of copying it. So, until a match
2753 fails, there's nothing to do since it might be the
2754 complete sequence, and thus, something we don't want
2757 if (data[i] == cont_seq[cmatch])
2760 if (cmatch == strlen(cont_seq))
2762 cmatch = 0; // complete match. just reset the counter
2765 it's possible for the ICS to not include the space
2766 at the end of the last word, making our [correct]
2767 join operation fuse two separate words. the server
2768 does this when the space occurs at the width setting.
2770 if (!buf_len || buf[buf_len-1] != ' ')
2781 match failed, so we have to copy what matched before
2782 falling through and copying this character. In reality,
2783 this will only ever be just the newline character, but
2784 it doesn't hurt to be precise.
2786 strncpy(bp, cont_seq, cmatch);
2798 buf[buf_len] = NULLCHAR;
2799 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2804 while (i < buf_len) {
2805 /* Deal with part of the TELNET option negotiation
2806 protocol. We refuse to do anything beyond the
2807 defaults, except that we allow the WILL ECHO option,
2808 which ICS uses to turn off password echoing when we are
2809 directly connected to it. We reject this option
2810 if localLineEditing mode is on (always on in xboard)
2811 and we are talking to port 23, which might be a real
2812 telnet server that will try to keep WILL ECHO on permanently.
2814 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816 unsigned char option;
2818 switch ((unsigned char) buf[++i]) {
2820 if (appData.debugMode)
2821 fprintf(debugFP, "\n<WILL ");
2822 switch (option = (unsigned char) buf[++i]) {
2824 if (appData.debugMode)
2825 fprintf(debugFP, "ECHO ");
2826 /* Reply only if this is a change, according
2827 to the protocol rules. */
2828 if (remoteEchoOption) break;
2829 if (appData.localLineEditing &&
2830 atoi(appData.icsPort) == TN_PORT) {
2831 TelnetRequest(TN_DONT, TN_ECHO);
2834 TelnetRequest(TN_DO, TN_ECHO);
2835 remoteEchoOption = TRUE;
2839 if (appData.debugMode)
2840 fprintf(debugFP, "%d ", option);
2841 /* Whatever this is, we don't want it. */
2842 TelnetRequest(TN_DONT, option);
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<WONT ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 if (appData.debugMode)
2852 fprintf(debugFP, "ECHO ");
2853 /* Reply only if this is a change, according
2854 to the protocol rules. */
2855 if (!remoteEchoOption) break;
2857 TelnetRequest(TN_DONT, TN_ECHO);
2858 remoteEchoOption = FALSE;
2861 if (appData.debugMode)
2862 fprintf(debugFP, "%d ", (unsigned char) option);
2863 /* Whatever this is, it must already be turned
2864 off, because we never agree to turn on
2865 anything non-default, so according to the
2866 protocol rules, we don't reply. */
2871 if (appData.debugMode)
2872 fprintf(debugFP, "\n<DO ");
2873 switch (option = (unsigned char) buf[++i]) {
2875 /* Whatever this is, we refuse to do it. */
2876 if (appData.debugMode)
2877 fprintf(debugFP, "%d ", option);
2878 TelnetRequest(TN_WONT, option);
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<DONT ");
2885 switch (option = (unsigned char) buf[++i]) {
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we are already not doing
2890 it, because we never agree to do anything
2891 non-default, so according to the protocol
2892 rules, we don't reply. */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<IAC ");
2899 /* Doubled IAC; pass it through */
2903 if (appData.debugMode)
2904 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905 /* Drop all other telnet commands on the floor */
2908 if (oldi > next_out)
2909 SendToPlayer(&buf[next_out], oldi - next_out);
2915 /* OK, this at least will *usually* work */
2916 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2920 if (loggedOn && !intfSet) {
2921 if (ics_type == ICS_ICC) {
2922 snprintf(str, MSG_SIZ,
2923 "/set-quietly interface %s\n/set-quietly style 12\n",
2925 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926 strcat(str, "/set-2 51 1\n/set seek 1\n");
2927 } else if (ics_type == ICS_CHESSNET) {
2928 snprintf(str, MSG_SIZ, "/style 12\n");
2930 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931 strcat(str, programVersion);
2932 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 strcat(str, "$iset nohighlight 1\n");
2938 strcat(str, "$iset lock 1\n$style 12\n");
2941 NotifyFrontendLogin();
2945 if (started == STARTED_COMMENT) {
2946 /* Accumulate characters in comment */
2947 parse[parse_pos++] = buf[i];
2948 if (buf[i] == '\n') {
2949 parse[parse_pos] = NULLCHAR;
2950 if(chattingPartner>=0) {
2952 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953 OutputChatMessage(chattingPartner, mess);
2954 chattingPartner = -1;
2955 next_out = i+1; // [HGM] suppress printing in ICS window
2957 if(!suppressKibitz) // [HGM] kibitz
2958 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960 int nrDigit = 0, nrAlph = 0, j;
2961 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963 parse[parse_pos] = NULLCHAR;
2964 // try to be smart: if it does not look like search info, it should go to
2965 // ICS interaction window after all, not to engine-output window.
2966 for(j=0; j<parse_pos; j++) { // count letters and digits
2967 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2969 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2971 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972 int depth=0; float score;
2973 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975 pvInfoList[forwardMostMove-1].depth = depth;
2976 pvInfoList[forwardMostMove-1].score = 100*score;
2978 OutputKibitz(suppressKibitz, parse);
2981 if(gameMode == IcsObserving) // restore original ICS messages
2982 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985 SendToPlayer(tmp, strlen(tmp));
2987 next_out = i+1; // [HGM] suppress printing in ICS window
2989 started = STARTED_NONE;
2991 /* Don't match patterns against characters in comment */
2996 if (started == STARTED_CHATTER) {
2997 if (buf[i] != '\n') {
2998 /* Don't match patterns against characters in chatter */
3002 started = STARTED_NONE;
3003 if(suppressKibitz) next_out = i+1;
3006 /* Kludge to deal with rcmd protocol */
3007 if (firstTime && looking_at(buf, &i, "\001*")) {
3008 DisplayFatalError(&buf[1], 0, 1);
3014 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017 if (appData.debugMode)
3018 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022 ics_type = ICS_FICS;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "ics_type %d\n", ics_type);
3028 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029 ics_type = ICS_CHESSNET;
3031 if (appData.debugMode)
3032 fprintf(debugFP, "ics_type %d\n", ics_type);
3037 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038 looking_at(buf, &i, "Logging you in as \"*\"") ||
3039 looking_at(buf, &i, "will be \"*\""))) {
3040 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3044 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047 DisplayIcsInteractionTitle(buf);
3048 have_set_title = TRUE;
3051 /* skip finger notes */
3052 if (started == STARTED_NONE &&
3053 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054 (buf[i] == '1' && buf[i+1] == '0')) &&
3055 buf[i+2] == ':' && buf[i+3] == ' ') {
3056 started = STARTED_CHATTER;
3062 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063 if(appData.seekGraph) {
3064 if(soughtPending && MatchSoughtLine(buf+i)) {
3065 i = strstr(buf+i, "rated") - buf;
3066 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 next_out = leftover_start = i;
3068 started = STARTED_CHATTER;
3069 suppressKibitz = TRUE;
3072 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073 && looking_at(buf, &i, "* ads displayed")) {
3074 soughtPending = FALSE;
3079 if(appData.autoRefresh) {
3080 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081 int s = (ics_type == ICS_ICC); // ICC format differs
3083 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085 looking_at(buf, &i, "*% "); // eat prompt
3086 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 next_out = i; // suppress
3091 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092 char *p = star_match[0];
3094 if(seekGraphUp) RemoveSeekAd(atoi(p));
3095 while(*p && *p++ != ' '); // next
3097 looking_at(buf, &i, "*% "); // eat prompt
3098 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 /* skip formula vars */
3106 if (started == STARTED_NONE &&
3107 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108 started = STARTED_CHATTER;
3113 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114 if (appData.autoKibitz && started == STARTED_NONE &&
3115 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3116 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3121 suppressKibitz = TRUE;
3122 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125 && (gameMode == IcsPlayingWhite)) ||
3126 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3128 started = STARTED_CHATTER; // own kibitz we simply discard
3130 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131 parse_pos = 0; parse[0] = NULLCHAR;
3132 savingComment = TRUE;
3133 suppressKibitz = gameMode != IcsObserving ? 2 :
3134 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3138 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140 && atoi(star_match[0])) {
3141 // suppress the acknowledgements of our own autoKibitz
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145 SendToPlayer(star_match[0], strlen(star_match[0]));
3146 if(looking_at(buf, &i, "*% ")) // eat prompt
3147 suppressKibitz = FALSE;
3151 } // [HGM] kibitz: end of patch
3153 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155 // [HGM] chat: intercept tells by users for which we have an open chat window
3157 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158 looking_at(buf, &i, "* whispers:") ||
3159 looking_at(buf, &i, "* kibitzes:") ||
3160 looking_at(buf, &i, "* shouts:") ||
3161 looking_at(buf, &i, "* c-shouts:") ||
3162 looking_at(buf, &i, "--> * ") ||
3163 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169 chattingPartner = -1;
3171 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174 talker[0] = '['; strcat(talker, "] ");
3175 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176 chattingPartner = p; break;
3179 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(!strcmp("kibitzes", chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 chattingPartner = p; break;
3186 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187 for(p=0; p<MAX_CHAT; p++) {
3188 if(!strcmp("whispers", chatPartner[p])) {
3189 talker[0] = '['; strcat(talker, "] ");
3190 chattingPartner = p; break;
3193 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194 if(buf[i-8] == '-' && buf[i-3] == 't')
3195 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196 if(!strcmp("c-shouts", chatPartner[p])) {
3197 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198 chattingPartner = p; break;
3201 if(chattingPartner < 0)
3202 for(p=0; p<MAX_CHAT; p++) {
3203 if(!strcmp("shouts", chatPartner[p])) {
3204 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207 chattingPartner = p; break;
3211 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213 talker[0] = 0; Colorize(ColorTell, FALSE);
3214 chattingPartner = p; break;
3216 if(chattingPartner<0) i = oldi; else {
3217 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 started = STARTED_COMMENT;
3221 parse_pos = 0; parse[0] = NULLCHAR;
3222 savingComment = 3 + chattingPartner; // counts as TRUE
3223 suppressKibitz = TRUE;
3226 } // [HGM] chat: end of patch
3229 if (appData.zippyTalk || appData.zippyPlay) {
3230 /* [DM] Backup address for color zippy lines */
3232 if (loggedOn == TRUE)
3233 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 } // [DM] 'else { ' deleted
3238 /* Regular tells and says */
3239 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240 looking_at(buf, &i, "* (your partner) tells you: ") ||
3241 looking_at(buf, &i, "* says: ") ||
3242 /* Don't color "message" or "messages" output */
3243 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244 looking_at(buf, &i, "*. * at *:*: ") ||
3245 looking_at(buf, &i, "--* (*:*): ") ||
3246 /* Message notifications (same color as tells) */
3247 looking_at(buf, &i, "* has left a message ") ||
3248 looking_at(buf, &i, "* just sent you a message:\n") ||
3249 /* Whispers and kibitzes */
3250 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251 looking_at(buf, &i, "* kibitzes: ") ||
3253 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255 if (tkind == 1 && strchr(star_match[0], ':')) {
3256 /* Avoid "tells you:" spoofs in channels */
3259 if (star_match[0][0] == NULLCHAR ||
3260 strchr(star_match[0], ' ') ||
3261 (tkind == 3 && strchr(star_match[1], ' '))) {
3262 /* Reject bogus matches */
3265 if (appData.colorize) {
3266 if (oldi > next_out) {
3267 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorTell, FALSE);
3273 curColor = ColorTell;
3276 Colorize(ColorKibitz, FALSE);
3277 curColor = ColorKibitz;
3280 p = strrchr(star_match[1], '(');
3287 Colorize(ColorChannel1, FALSE);
3288 curColor = ColorChannel1;
3290 Colorize(ColorChannel, FALSE);
3291 curColor = ColorChannel;
3295 curColor = ColorNormal;
3299 if (started == STARTED_NONE && appData.autoComment &&
3300 (gameMode == IcsObserving ||
3301 gameMode == IcsPlayingWhite ||
3302 gameMode == IcsPlayingBlack)) {
3303 parse_pos = i - oldi;
3304 memcpy(parse, &buf[oldi], parse_pos);
3305 parse[parse_pos] = NULLCHAR;
3306 started = STARTED_COMMENT;
3307 savingComment = TRUE;
3309 started = STARTED_CHATTER;
3310 savingComment = FALSE;
3317 if (looking_at(buf, &i, "* s-shouts: ") ||
3318 looking_at(buf, &i, "* c-shouts: ")) {
3319 if (appData.colorize) {
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorSShout, FALSE);
3325 curColor = ColorSShout;
3328 started = STARTED_CHATTER;
3332 if (looking_at(buf, &i, "--->")) {
3337 if (looking_at(buf, &i, "* shouts: ") ||
3338 looking_at(buf, &i, "--> ")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorShout, FALSE);
3345 curColor = ColorShout;
3348 started = STARTED_CHATTER;
3352 if (looking_at( buf, &i, "Challenge:")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorChallenge, FALSE);
3359 curColor = ColorChallenge;
3365 if (looking_at(buf, &i, "* offers you") ||
3366 looking_at(buf, &i, "* offers to be") ||
3367 looking_at(buf, &i, "* would like to") ||
3368 looking_at(buf, &i, "* requests to") ||
3369 looking_at(buf, &i, "Your opponent offers") ||
3370 looking_at(buf, &i, "Your opponent requests")) {
3372 if (appData.colorize) {
3373 if (oldi > next_out) {
3374 SendToPlayer(&buf[next_out], oldi - next_out);
3377 Colorize(ColorRequest, FALSE);
3378 curColor = ColorRequest;
3383 if (looking_at(buf, &i, "* (*) seeking")) {
3384 if (appData.colorize) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 Colorize(ColorSeek, FALSE);
3390 curColor = ColorSeek;
3395 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397 if (looking_at(buf, &i, "\\ ")) {
3398 if (prevColor != ColorNormal) {
3399 if (oldi > next_out) {
3400 SendToPlayer(&buf[next_out], oldi - next_out);
3403 Colorize(prevColor, TRUE);
3404 curColor = prevColor;
3406 if (savingComment) {
3407 parse_pos = i - oldi;
3408 memcpy(parse, &buf[oldi], parse_pos);
3409 parse[parse_pos] = NULLCHAR;
3410 started = STARTED_COMMENT;
3411 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412 chattingPartner = savingComment - 3; // kludge to remember the box
3414 started = STARTED_CHATTER;
3419 if (looking_at(buf, &i, "Black Strength :") ||
3420 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421 looking_at(buf, &i, "<10>") ||
3422 looking_at(buf, &i, "#@#")) {
3423 /* Wrong board style */
3425 SendToICS(ics_prefix);
3426 SendToICS("set style 12\n");
3427 SendToICS(ics_prefix);
3428 SendToICS("refresh\n");
3432 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434 have_sent_ICS_logon = 1;
3438 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439 (looking_at(buf, &i, "\n<12> ") ||
3440 looking_at(buf, &i, "<12> "))) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 started = STARTED_BOARD;
3451 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452 looking_at(buf, &i, "<b1> ")) {
3453 if (oldi > next_out) {
3454 SendToPlayer(&buf[next_out], oldi - next_out);
3457 started = STARTED_HOLDINGS;
3462 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464 /* Header for a move list -- first line */
3466 switch (ics_getting_history) {
3470 case BeginningOfGame:
3471 /* User typed "moves" or "oldmoves" while we
3472 were idle. Pretend we asked for these
3473 moves and soak them up so user can step
3474 through them and/or save them.
3477 gameMode = IcsObserving;
3480 ics_getting_history = H_GOT_UNREQ_HEADER;
3482 case EditGame: /*?*/
3483 case EditPosition: /*?*/
3484 /* Should above feature work in these modes too? */
3485 /* For now it doesn't */
3486 ics_getting_history = H_GOT_UNWANTED_HEADER;
3489 ics_getting_history = H_GOT_UNWANTED_HEADER;
3494 /* Is this the right one? */
3495 if (gameInfo.white && gameInfo.black &&
3496 strcmp(gameInfo.white, star_match[0]) == 0 &&
3497 strcmp(gameInfo.black, star_match[2]) == 0) {
3499 ics_getting_history = H_GOT_REQ_HEADER;
3502 case H_GOT_REQ_HEADER:
3503 case H_GOT_UNREQ_HEADER:
3504 case H_GOT_UNWANTED_HEADER:
3505 case H_GETTING_MOVES:
3506 /* Should not happen */
3507 DisplayError(_("Error gathering move list: two headers"), 0);
3508 ics_getting_history = H_FALSE;
3512 /* Save player ratings into gameInfo if needed */
3513 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515 (gameInfo.whiteRating == -1 ||
3516 gameInfo.blackRating == -1)) {
3518 gameInfo.whiteRating = string_to_rating(star_match[1]);
3519 gameInfo.blackRating = string_to_rating(star_match[3]);
3520 if (appData.debugMode)
3521 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522 gameInfo.whiteRating, gameInfo.blackRating);
3527 if (looking_at(buf, &i,
3528 "* * match, initial time: * minute*, increment: * second")) {
3529 /* Header for a move list -- second line */
3530 /* Initial board will follow if this is a wild game */
3531 if (gameInfo.event != NULL) free(gameInfo.event);
3532 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533 gameInfo.event = StrSave(str);
3534 /* [HGM] we switched variant. Translate boards if needed. */
3535 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3539 if (looking_at(buf, &i, "Move ")) {
3540 /* Beginning of a move list */
3541 switch (ics_getting_history) {
3543 /* Normally should not happen */
3544 /* Maybe user hit reset while we were parsing */
3547 /* Happens if we are ignoring a move list that is not
3548 * the one we just requested. Common if the user
3549 * tries to observe two games without turning off
3552 case H_GETTING_MOVES:
3553 /* Should not happen */
3554 DisplayError(_("Error gathering move list: nested"), 0);
3555 ics_getting_history = H_FALSE;
3557 case H_GOT_REQ_HEADER:
3558 ics_getting_history = H_GETTING_MOVES;
3559 started = STARTED_MOVES;
3561 if (oldi > next_out) {
3562 SendToPlayer(&buf[next_out], oldi - next_out);
3565 case H_GOT_UNREQ_HEADER:
3566 ics_getting_history = H_GETTING_MOVES;
3567 started = STARTED_MOVES_NOHIDE;
3570 case H_GOT_UNWANTED_HEADER:
3571 ics_getting_history = H_FALSE;
3577 if (looking_at(buf, &i, "% ") ||
3578 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581 soughtPending = FALSE;
3585 if(suppressKibitz) next_out = i;
3586 savingComment = FALSE;
3590 case STARTED_MOVES_NOHIDE:
3591 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592 parse[parse_pos + i - oldi] = NULLCHAR;
3593 ParseGameHistory(parse);
3595 if (appData.zippyPlay && first.initDone) {
3596 FeedMovesToProgram(&first, forwardMostMove);
3597 if (gameMode == IcsPlayingWhite) {
3598 if (WhiteOnMove(forwardMostMove)) {
3599 if (first.sendTime) {
3600 if (first.useColors) {
3601 SendToProgram("black\n", &first);
3603 SendTimeRemaining(&first, TRUE);
3605 if (first.useColors) {
3606 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609 first.maybeThinking = TRUE;
3611 if (first.usePlayother) {
3612 if (first.sendTime) {
3613 SendTimeRemaining(&first, TRUE);
3615 SendToProgram("playother\n", &first);
3621 } else if (gameMode == IcsPlayingBlack) {
3622 if (!WhiteOnMove(forwardMostMove)) {
3623 if (first.sendTime) {
3624 if (first.useColors) {
3625 SendToProgram("white\n", &first);
3627 SendTimeRemaining(&first, FALSE);
3629 if (first.useColors) {
3630 SendToProgram("black\n", &first);
3632 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633 first.maybeThinking = TRUE;
3635 if (first.usePlayother) {
3636 if (first.sendTime) {
3637 SendTimeRemaining(&first, FALSE);
3639 SendToProgram("playother\n", &first);
3648 if (gameMode == IcsObserving && ics_gamenum == -1) {
3649 /* Moves came from oldmoves or moves command
3650 while we weren't doing anything else.
3652 currentMove = forwardMostMove;
3653 ClearHighlights();/*!!could figure this out*/
3654 flipView = appData.flipView;
3655 DrawPosition(TRUE, boards[currentMove]);
3656 DisplayBothClocks();
3657 snprintf(str, MSG_SIZ, "%s %s %s",
3658 gameInfo.white, _("vs."), gameInfo.black);
3662 /* Moves were history of an active game */
3663 if (gameInfo.resultDetails != NULL) {
3664 free(gameInfo.resultDetails);
3665 gameInfo.resultDetails = NULL;
3668 HistorySet(parseList, backwardMostMove,
3669 forwardMostMove, currentMove-1);
3670 DisplayMove(currentMove - 1);
3671 if (started == STARTED_MOVES) next_out = i;
3672 started = STARTED_NONE;
3673 ics_getting_history = H_FALSE;
3676 case STARTED_OBSERVE:
3677 started = STARTED_NONE;
3678 SendToICS(ics_prefix);
3679 SendToICS("refresh\n");
3685 if(bookHit) { // [HGM] book: simulate book reply
3686 static char bookMove[MSG_SIZ]; // a bit generous?
3688 programStats.nodes = programStats.depth = programStats.time =
3689 programStats.score = programStats.got_only_move = 0;
3690 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693 strcat(bookMove, bookHit);
3694 HandleMachineMove(bookMove, &first);
3699 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700 started == STARTED_HOLDINGS ||
3701 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702 /* Accumulate characters in move list or board */
3703 parse[parse_pos++] = buf[i];
3706 /* Start of game messages. Mostly we detect start of game
3707 when the first board image arrives. On some versions
3708 of the ICS, though, we need to do a "refresh" after starting
3709 to observe in order to get the current board right away. */
3710 if (looking_at(buf, &i, "Adding game * to observation list")) {
3711 started = STARTED_OBSERVE;
3715 /* Handle auto-observe */
3716 if (appData.autoObserve &&
3717 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720 /* Choose the player that was highlighted, if any. */
3721 if (star_match[0][0] == '\033' ||
3722 star_match[1][0] != '\033') {
3723 player = star_match[0];
3725 player = star_match[2];
3727 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728 ics_prefix, StripHighlightAndTitle(player));
3731 /* Save ratings from notify string */
3732 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733 player1Rating = string_to_rating(star_match[1]);
3734 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735 player2Rating = string_to_rating(star_match[3]);
3737 if (appData.debugMode)
3739 "Ratings from 'Game notification:' %s %d, %s %d\n",
3740 player1Name, player1Rating,
3741 player2Name, player2Rating);
3746 /* Deal with automatic examine mode after a game,
3747 and with IcsObserving -> IcsExamining transition */
3748 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749 looking_at(buf, &i, "has made you an examiner of game *")) {
3751 int gamenum = atoi(star_match[0]);
3752 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753 gamenum == ics_gamenum) {
3754 /* We were already playing or observing this game;
3755 no need to refetch history */
3756 gameMode = IcsExamining;
3758 pauseExamForwardMostMove = forwardMostMove;
3759 } else if (currentMove < forwardMostMove) {
3760 ForwardInner(forwardMostMove);
3763 /* I don't think this case really can happen */
3764 SendToICS(ics_prefix);
3765 SendToICS("refresh\n");
3770 /* Error messages */
3771 // if (ics_user_moved) {
3772 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773 if (looking_at(buf, &i, "Illegal move") ||
3774 looking_at(buf, &i, "Not a legal move") ||
3775 looking_at(buf, &i, "Your king is in check") ||
3776 looking_at(buf, &i, "It isn't your turn") ||
3777 looking_at(buf, &i, "It is not your move")) {
3779 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780 currentMove = forwardMostMove-1;
3781 DisplayMove(currentMove - 1); /* before DMError */
3782 DrawPosition(FALSE, boards[currentMove]);
3783 SwitchClocks(forwardMostMove-1); // [HGM] race
3784 DisplayBothClocks();
3786 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3792 if (looking_at(buf, &i, "still have time") ||
3793 looking_at(buf, &i, "not out of time") ||
3794 looking_at(buf, &i, "either player is out of time") ||
3795 looking_at(buf, &i, "has timeseal; checking")) {
3796 /* We must have called his flag a little too soon */
3797 whiteFlag = blackFlag = FALSE;
3801 if (looking_at(buf, &i, "added * seconds to") ||
3802 looking_at(buf, &i, "seconds were added to")) {
3803 /* Update the clocks */
3804 SendToICS(ics_prefix);
3805 SendToICS("refresh\n");
3809 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810 ics_clock_paused = TRUE;
3815 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816 ics_clock_paused = FALSE;
3821 /* Grab player ratings from the Creating: message.
3822 Note we have to check for the special case when
3823 the ICS inserts things like [white] or [black]. */
3824 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827 0 player 1 name (not necessarily white)
3829 2 empty, white, or black (IGNORED)
3830 3 player 2 name (not necessarily black)
3833 The names/ratings are sorted out when the game
3834 actually starts (below).
3836 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837 player1Rating = string_to_rating(star_match[1]);
3838 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839 player2Rating = string_to_rating(star_match[4]);
3841 if (appData.debugMode)
3843 "Ratings from 'Creating:' %s %d, %s %d\n",
3844 player1Name, player1Rating,
3845 player2Name, player2Rating);
3850 /* Improved generic start/end-of-game messages */
3851 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853 /* If tkind == 0: */
3854 /* star_match[0] is the game number */
3855 /* [1] is the white player's name */
3856 /* [2] is the black player's name */
3857 /* For end-of-game: */
3858 /* [3] is the reason for the game end */
3859 /* [4] is a PGN end game-token, preceded by " " */
3860 /* For start-of-game: */
3861 /* [3] begins with "Creating" or "Continuing" */
3862 /* [4] is " *" or empty (don't care). */
3863 int gamenum = atoi(star_match[0]);
3864 char *whitename, *blackname, *why, *endtoken;
3865 ChessMove endtype = EndOfFile;
3868 whitename = star_match[1];
3869 blackname = star_match[2];
3870 why = star_match[3];
3871 endtoken = star_match[4];
3873 whitename = star_match[1];
3874 blackname = star_match[3];
3875 why = star_match[5];
3876 endtoken = star_match[6];
3879 /* Game start messages */
3880 if (strncmp(why, "Creating ", 9) == 0 ||
3881 strncmp(why, "Continuing ", 11) == 0) {
3882 gs_gamenum = gamenum;
3883 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 if (appData.zippyPlay) {
3888 ZippyGameStart(whitename, blackname);
3891 partnerBoardValid = FALSE; // [HGM] bughouse
3895 /* Game end messages */
3896 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897 ics_gamenum != gamenum) {
3900 while (endtoken[0] == ' ') endtoken++;
3901 switch (endtoken[0]) {
3904 endtype = GameUnfinished;
3907 endtype = BlackWins;
3910 if (endtoken[1] == '/')
3911 endtype = GameIsDrawn;
3913 endtype = WhiteWins;
3916 GameEnds(endtype, why, GE_ICS);
3918 if (appData.zippyPlay && first.initDone) {
3919 ZippyGameEnd(endtype, why);
3920 if (first.pr == NoProc) {
3921 /* Start the next process early so that we'll
3922 be ready for the next challenge */
3923 StartChessProgram(&first);
3925 /* Send "new" early, in case this command takes
3926 a long time to finish, so that we'll be ready
3927 for the next challenge. */
3928 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3932 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3936 if (looking_at(buf, &i, "Removing game * from observation") ||
3937 looking_at(buf, &i, "no longer observing game *") ||
3938 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939 if (gameMode == IcsObserving &&
3940 atoi(star_match[0]) == ics_gamenum)
3942 /* icsEngineAnalyze */
3943 if (appData.icsEngineAnalyze) {
3950 ics_user_moved = FALSE;
3955 if (looking_at(buf, &i, "no longer examining game *")) {
3956 if (gameMode == IcsExamining &&
3957 atoi(star_match[0]) == ics_gamenum)
3961 ics_user_moved = FALSE;
3966 /* Advance leftover_start past any newlines we find,
3967 so only partial lines can get reparsed */
3968 if (looking_at(buf, &i, "\n")) {
3969 prevColor = curColor;
3970 if (curColor != ColorNormal) {
3971 if (oldi > next_out) {
3972 SendToPlayer(&buf[next_out], oldi - next_out);
3975 Colorize(ColorNormal, FALSE);
3976 curColor = ColorNormal;
3978 if (started == STARTED_BOARD) {
3979 started = STARTED_NONE;
3980 parse[parse_pos] = NULLCHAR;
3981 ParseBoard12(parse);
3984 /* Send premove here */
3985 if (appData.premove) {
3987 if (currentMove == 0 &&
3988 gameMode == IcsPlayingWhite &&
3989 appData.premoveWhite) {
3990 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991 if (appData.debugMode)
3992 fprintf(debugFP, "Sending premove:\n");
3994 } else if (currentMove == 1 &&
3995 gameMode == IcsPlayingBlack &&
3996 appData.premoveBlack) {
3997 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998 if (appData.debugMode)
3999 fprintf(debugFP, "Sending premove:\n");
4001 } else if (gotPremove) {
4003 ClearPremoveHighlights();
4004 if (appData.debugMode)
4005 fprintf(debugFP, "Sending premove:\n");
4006 UserMoveEvent(premoveFromX, premoveFromY,
4007 premoveToX, premoveToY,
4012 /* Usually suppress following prompt */
4013 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015 if (looking_at(buf, &i, "*% ")) {
4016 savingComment = FALSE;
4021 } else if (started == STARTED_HOLDINGS) {
4023 char new_piece[MSG_SIZ];
4024 started = STARTED_NONE;
4025 parse[parse_pos] = NULLCHAR;
4026 if (appData.debugMode)
4027 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028 parse, currentMove);
4029 if (sscanf(parse, " game %d", &gamenum) == 1) {
4030 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031 if (gameInfo.variant == VariantNormal) {
4032 /* [HGM] We seem to switch variant during a game!
4033 * Presumably no holdings were displayed, so we have
4034 * to move the position two files to the right to
4035 * create room for them!
4037 VariantClass newVariant;
4038 switch(gameInfo.boardWidth) { // base guess on board width
4039 case 9: newVariant = VariantShogi; break;
4040 case 10: newVariant = VariantGreat; break;
4041 default: newVariant = VariantCrazyhouse; break;
4043 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044 /* Get a move list just to see the header, which
4045 will tell us whether this is really bug or zh */
4046 if (ics_getting_history == H_FALSE) {
4047 ics_getting_history = H_REQUESTED;
4048 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4052 new_piece[0] = NULLCHAR;
4053 sscanf(parse, "game %d white [%s black [%s <- %s",
4054 &gamenum, white_holding, black_holding,
4056 white_holding[strlen(white_holding)-1] = NULLCHAR;
4057 black_holding[strlen(black_holding)-1] = NULLCHAR;
4058 /* [HGM] copy holdings to board holdings area */
4059 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 if (appData.zippyPlay && first.initDone) {
4064 ZippyHoldings(white_holding, black_holding,
4068 if (tinyLayout || smallLayout) {
4069 char wh[16], bh[16];
4070 PackHolding(wh, white_holding);
4071 PackHolding(bh, black_holding);
4072 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073 gameInfo.white, gameInfo.black);
4075 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076 gameInfo.white, white_holding, _("vs."),
4077 gameInfo.black, black_holding);
4079 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080 DrawPosition(FALSE, boards[currentMove]);
4082 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083 sscanf(parse, "game %d white [%s black [%s <- %s",
4084 &gamenum, white_holding, black_holding,
4086 white_holding[strlen(white_holding)-1] = NULLCHAR;
4087 black_holding[strlen(black_holding)-1] = NULLCHAR;
4088 /* [HGM] copy holdings to partner-board holdings area */
4089 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4096 /* Suppress following prompt */
4097 if (looking_at(buf, &i, "*% ")) {
4098 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099 savingComment = FALSE;
4107 i++; /* skip unparsed character and loop back */
4110 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 // SendToPlayer(&buf[next_out], i - next_out);
4113 started != STARTED_HOLDINGS && leftover_start > next_out) {
4114 SendToPlayer(&buf[next_out], leftover_start - next_out);
4118 leftover_len = buf_len - leftover_start;
4119 /* if buffer ends with something we couldn't parse,
4120 reparse it after appending the next read */
4122 } else if (count == 0) {
4123 RemoveInputSource(isr);
4124 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126 DisplayFatalError(_("Error reading from ICS"), error, 1);
4131 /* Board style 12 looks like this:
4133 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4135 * The "<12> " is stripped before it gets to this routine. The two
4136 * trailing 0's (flip state and clock ticking) are later addition, and
4137 * some chess servers may not have them, or may have only the first.
4138 * Additional trailing fields may be added in the future.
4141 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4143 #define RELATION_OBSERVING_PLAYED 0
4144 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE 1
4146 #define RELATION_PLAYING_NOTMYMOVE -1
4147 #define RELATION_EXAMINING 2
4148 #define RELATION_ISOLATED_BOARD -3
4149 #define RELATION_STARTING_POSITION -4 /* FICS only */
4152 ParseBoard12 (char *string)
4154 GameMode newGameMode;
4155 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158 char to_play, board_chars[200];
4159 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160 char black[32], white[32];
4162 int prevMove = currentMove;
4165 int fromX, fromY, toX, toY;
4167 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168 char *bookHit = NULL; // [HGM] book
4169 Boolean weird = FALSE, reqFlag = FALSE;
4171 fromX = fromY = toX = toY = -1;
4175 if (appData.debugMode)
4176 fprintf(debugFP, _("Parsing board: %s\n"), string);
4178 move_str[0] = NULLCHAR;
4179 elapsed_time[0] = NULLCHAR;
4180 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183 if(string[i] == ' ') { ranks++; files = 0; }
4185 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4188 for(j = 0; j <i; j++) board_chars[j] = string[j];
4189 board_chars[i] = '\0';
4192 n = sscanf(string, PATTERN, &to_play, &double_push,
4193 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194 &gamenum, white, black, &relation, &basetime, &increment,
4195 &white_stren, &black_stren, &white_time, &black_time,
4196 &moveNum, str, elapsed_time, move_str, &ics_flip,
4200 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201 DisplayError(str, 0);
4205 /* Convert the move number to internal form */
4206 moveNum = (moveNum - 1) * 2;
4207 if (to_play == 'B') moveNum++;
4208 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4215 case RELATION_OBSERVING_PLAYED:
4216 case RELATION_OBSERVING_STATIC:
4217 if (gamenum == -1) {
4218 /* Old ICC buglet */
4219 relation = RELATION_OBSERVING_STATIC;
4221 newGameMode = IcsObserving;
4223 case RELATION_PLAYING_MYMOVE:
4224 case RELATION_PLAYING_NOTMYMOVE:
4226 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227 IcsPlayingWhite : IcsPlayingBlack;
4228 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230 case RELATION_EXAMINING:
4231 newGameMode = IcsExamining;
4233 case RELATION_ISOLATED_BOARD:
4235 /* Just display this board. If user was doing something else,
4236 we will forget about it until the next board comes. */
4237 newGameMode = IcsIdle;
4239 case RELATION_STARTING_POSITION:
4240 newGameMode = gameMode;
4244 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248 for (k = 0; k < ranks; k++) {
4249 for (j = 0; j < files; j++)
4250 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251 if(gameInfo.holdingsWidth > 1) {
4252 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4256 CopyBoard(partnerBoard, board);
4257 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261 if(toSqr = strchr(str, '-')) {
4262 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4268 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4269 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4270 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4271 DisplayMessage(partnerStatus, "");
4272 partnerBoardValid = TRUE;
4276 /* Modify behavior for initial board display on move listing
4279 switch (ics_getting_history) {
4283 case H_GOT_REQ_HEADER:
4284 case H_GOT_UNREQ_HEADER:
4285 /* This is the initial position of the current game */
4286 gamenum = ics_gamenum;
4287 moveNum = 0; /* old ICS bug workaround */
4288 if (to_play == 'B') {
4289 startedFromSetupPosition = TRUE;
4290 blackPlaysFirst = TRUE;
4292 if (forwardMostMove == 0) forwardMostMove = 1;
4293 if (backwardMostMove == 0) backwardMostMove = 1;
4294 if (currentMove == 0) currentMove = 1;
4296 newGameMode = gameMode;
4297 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4299 case H_GOT_UNWANTED_HEADER:
4300 /* This is an initial board that we don't want */
4302 case H_GETTING_MOVES:
4303 /* Should not happen */
4304 DisplayError(_("Error gathering move list: extra board"), 0);
4305 ics_getting_history = H_FALSE;
4309 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4310 weird && (int)gameInfo.variant < (int)VariantShogi) {
4311 /* [HGM] We seem to have switched variant unexpectedly
4312 * Try to guess new variant from board size
4314 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4315 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4316 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4317 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4318 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4319 if(!weird) newVariant = VariantNormal;
4320 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4321 /* Get a move list just to see the header, which
4322 will tell us whether this is really bug or zh */
4323 if (ics_getting_history == H_FALSE) {
4324 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4325 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4330 /* Take action if this is the first board of a new game, or of a
4331 different game than is currently being displayed. */
4332 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4333 relation == RELATION_ISOLATED_BOARD) {
4335 /* Forget the old game and get the history (if any) of the new one */
4336 if (gameMode != BeginningOfGame) {
4340 if (appData.autoRaiseBoard) BoardToTop();
4342 if (gamenum == -1) {
4343 newGameMode = IcsIdle;
4344 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4345 appData.getMoveList && !reqFlag) {
4346 /* Need to get game history */
4347 ics_getting_history = H_REQUESTED;
4348 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352 /* Initially flip the board to have black on the bottom if playing
4353 black or if the ICS flip flag is set, but let the user change
4354 it with the Flip View button. */
4355 flipView = appData.autoFlipView ?
4356 (newGameMode == IcsPlayingBlack) || ics_flip :
4359 /* Done with values from previous mode; copy in new ones */
4360 gameMode = newGameMode;
4362 ics_gamenum = gamenum;
4363 if (gamenum == gs_gamenum) {
4364 int klen = strlen(gs_kind);
4365 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4366 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4367 gameInfo.event = StrSave(str);
4369 gameInfo.event = StrSave("ICS game");
4371 gameInfo.site = StrSave(appData.icsHost);
4372 gameInfo.date = PGNDate();
4373 gameInfo.round = StrSave("-");
4374 gameInfo.white = StrSave(white);
4375 gameInfo.black = StrSave(black);
4376 timeControl = basetime * 60 * 1000;
4378 timeIncrement = increment * 1000;
4379 movesPerSession = 0;
4380 gameInfo.timeControl = TimeControlTagValue();
4381 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4382 if (appData.debugMode) {
4383 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4384 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4385 setbuf(debugFP, NULL);
4388 gameInfo.outOfBook = NULL;
4390 /* Do we have the ratings? */
4391 if (strcmp(player1Name, white) == 0 &&
4392 strcmp(player2Name, black) == 0) {
4393 if (appData.debugMode)
4394 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4395 player1Rating, player2Rating);
4396 gameInfo.whiteRating = player1Rating;
4397 gameInfo.blackRating = player2Rating;
4398 } else if (strcmp(player2Name, white) == 0 &&
4399 strcmp(player1Name, black) == 0) {
4400 if (appData.debugMode)
4401 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4402 player2Rating, player1Rating);
4403 gameInfo.whiteRating = player2Rating;
4404 gameInfo.blackRating = player1Rating;
4406 player1Name[0] = player2Name[0] = NULLCHAR;
4408 /* Silence shouts if requested */
4409 if (appData.quietPlay &&
4410 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4411 SendToICS(ics_prefix);
4412 SendToICS("set shout 0\n");
4416 /* Deal with midgame name changes */
4418 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4419 if (gameInfo.white) free(gameInfo.white);
4420 gameInfo.white = StrSave(white);
4422 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4423 if (gameInfo.black) free(gameInfo.black);
4424 gameInfo.black = StrSave(black);
4428 /* Throw away game result if anything actually changes in examine mode */
4429 if (gameMode == IcsExamining && !newGame) {
4430 gameInfo.result = GameUnfinished;
4431 if (gameInfo.resultDetails != NULL) {
4432 free(gameInfo.resultDetails);
4433 gameInfo.resultDetails = NULL;
4437 /* In pausing && IcsExamining mode, we ignore boards coming
4438 in if they are in a different variation than we are. */
4439 if (pauseExamInvalid) return;
4440 if (pausing && gameMode == IcsExamining) {
4441 if (moveNum <= pauseExamForwardMostMove) {
4442 pauseExamInvalid = TRUE;
4443 forwardMostMove = pauseExamForwardMostMove;
4448 if (appData.debugMode) {
4449 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4451 /* Parse the board */
4452 for (k = 0; k < ranks; k++) {
4453 for (j = 0; j < files; j++)
4454 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4455 if(gameInfo.holdingsWidth > 1) {
4456 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4457 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4460 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4461 board[5][BOARD_RGHT+1] = WhiteAngel;
4462 board[6][BOARD_RGHT+1] = WhiteMarshall;
4463 board[1][0] = BlackMarshall;
4464 board[2][0] = BlackAngel;
4465 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4467 CopyBoard(boards[moveNum], board);
4468 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4470 startedFromSetupPosition =
4471 !CompareBoards(board, initialPosition);
4472 if(startedFromSetupPosition)
4473 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4476 /* [HGM] Set castling rights. Take the outermost Rooks,
4477 to make it also work for FRC opening positions. Note that board12
4478 is really defective for later FRC positions, as it has no way to
4479 indicate which Rook can castle if they are on the same side of King.
4480 For the initial position we grant rights to the outermost Rooks,
4481 and remember thos rights, and we then copy them on positions
4482 later in an FRC game. This means WB might not recognize castlings with
4483 Rooks that have moved back to their original position as illegal,
4484 but in ICS mode that is not its job anyway.
4486 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4487 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4489 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4490 if(board[0][i] == WhiteRook) j = i;
4491 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4492 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4493 if(board[0][i] == WhiteRook) j = i;
4494 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4497 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4502 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4503 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4504 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4505 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4506 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4507 if(board[BOARD_HEIGHT-1][k] == bKing)
4508 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4509 if(gameInfo.variant == VariantTwoKings) {
4510 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4511 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4512 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4515 r = boards[moveNum][CASTLING][0] = initialRights[0];
4516 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4517 r = boards[moveNum][CASTLING][1] = initialRights[1];
4518 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4519 r = boards[moveNum][CASTLING][3] = initialRights[3];
4520 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4521 r = boards[moveNum][CASTLING][4] = initialRights[4];
4522 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4523 /* wildcastle kludge: always assume King has rights */
4524 r = boards[moveNum][CASTLING][2] = initialRights[2];
4525 r = boards[moveNum][CASTLING][5] = initialRights[5];
4527 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4528 boards[moveNum][EP_STATUS] = EP_NONE;
4529 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4530 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4531 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4534 if (ics_getting_history == H_GOT_REQ_HEADER ||
4535 ics_getting_history == H_GOT_UNREQ_HEADER) {
4536 /* This was an initial position from a move list, not
4537 the current position */
4541 /* Update currentMove and known move number limits */
4542 newMove = newGame || moveNum > forwardMostMove;
4545 forwardMostMove = backwardMostMove = currentMove = moveNum;
4546 if (gameMode == IcsExamining && moveNum == 0) {
4547 /* Workaround for ICS limitation: we are not told the wild
4548 type when starting to examine a game. But if we ask for
4549 the move list, the move list header will tell us */
4550 ics_getting_history = H_REQUESTED;
4551 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4554 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4555 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4557 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4558 /* [HGM] applied this also to an engine that is silently watching */
4559 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4560 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4561 gameInfo.variant == currentlyInitializedVariant) {
4562 takeback = forwardMostMove - moveNum;
4563 for (i = 0; i < takeback; i++) {
4564 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4565 SendToProgram("undo\n", &first);
4570 forwardMostMove = moveNum;
4571 if (!pausing || currentMove > forwardMostMove)
4572 currentMove = forwardMostMove;
4574 /* New part of history that is not contiguous with old part */
4575 if (pausing && gameMode == IcsExamining) {
4576 pauseExamInvalid = TRUE;
4577 forwardMostMove = pauseExamForwardMostMove;
4580 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4582 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4583 // [HGM] when we will receive the move list we now request, it will be
4584 // fed to the engine from the first move on. So if the engine is not
4585 // in the initial position now, bring it there.
4586 InitChessProgram(&first, 0);
4589 ics_getting_history = H_REQUESTED;
4590 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4593 forwardMostMove = backwardMostMove = currentMove = moveNum;
4596 /* Update the clocks */
4597 if (strchr(elapsed_time, '.')) {
4599 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4600 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4602 /* Time is in seconds */
4603 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4604 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4609 if (appData.zippyPlay && newGame &&
4610 gameMode != IcsObserving && gameMode != IcsIdle &&
4611 gameMode != IcsExamining)
4612 ZippyFirstBoard(moveNum, basetime, increment);
4615 /* Put the move on the move list, first converting
4616 to canonical algebraic form. */
4618 if (appData.debugMode) {
4619 if (appData.debugMode) { int f = forwardMostMove;
4620 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4621 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4622 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4624 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4625 fprintf(debugFP, "moveNum = %d\n", moveNum);
4626 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4627 setbuf(debugFP, NULL);
4629 if (moveNum <= backwardMostMove) {
4630 /* We don't know what the board looked like before
4632 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4633 strcat(parseList[moveNum - 1], " ");
4634 strcat(parseList[moveNum - 1], elapsed_time);
4635 moveList[moveNum - 1][0] = NULLCHAR;
4636 } else if (strcmp(move_str, "none") == 0) {
4637 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4638 /* Again, we don't know what the board looked like;
4639 this is really the start of the game. */
4640 parseList[moveNum - 1][0] = NULLCHAR;
4641 moveList[moveNum - 1][0] = NULLCHAR;
4642 backwardMostMove = moveNum;
4643 startedFromSetupPosition = TRUE;
4644 fromX = fromY = toX = toY = -1;
4646 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4647 // So we parse the long-algebraic move string in stead of the SAN move
4648 int valid; char buf[MSG_SIZ], *prom;
4650 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4651 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4652 // str looks something like "Q/a1-a2"; kill the slash
4654 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4655 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4656 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4657 strcat(buf, prom); // long move lacks promo specification!
4658 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4659 if(appData.debugMode)
4660 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4661 safeStrCpy(move_str, buf, MSG_SIZ);
4663 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4664 &fromX, &fromY, &toX, &toY, &promoChar)
4665 || ParseOneMove(buf, moveNum - 1, &moveType,
4666 &fromX, &fromY, &toX, &toY, &promoChar);
4667 // end of long SAN patch
4669 (void) CoordsToAlgebraic(boards[moveNum - 1],
4670 PosFlags(moveNum - 1),
4671 fromY, fromX, toY, toX, promoChar,
4672 parseList[moveNum-1]);
4673 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4679 if(gameInfo.variant != VariantShogi)
4680 strcat(parseList[moveNum - 1], "+");
4683 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4684 strcat(parseList[moveNum - 1], "#");
4687 strcat(parseList[moveNum - 1], " ");
4688 strcat(parseList[moveNum - 1], elapsed_time);
4689 /* currentMoveString is set as a side-effect of ParseOneMove */
4690 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4691 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4692 strcat(moveList[moveNum - 1], "\n");
4694 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4695 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4696 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4697 ChessSquare old, new = boards[moveNum][k][j];
4698 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4699 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4700 if(old == new) continue;
4701 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4702 else if(new == WhiteWazir || new == BlackWazir) {
4703 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4704 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4705 else boards[moveNum][k][j] = old; // preserve type of Gold
4706 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4707 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4710 /* Move from ICS was illegal!? Punt. */
4711 if (appData.debugMode) {
4712 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4713 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4715 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4716 strcat(parseList[moveNum - 1], " ");
4717 strcat(parseList[moveNum - 1], elapsed_time);
4718 moveList[moveNum - 1][0] = NULLCHAR;
4719 fromX = fromY = toX = toY = -1;
4722 if (appData.debugMode) {
4723 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4724 setbuf(debugFP, NULL);
4728 /* Send move to chess program (BEFORE animating it). */
4729 if (appData.zippyPlay && !newGame && newMove &&
4730 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4732 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4733 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4734 if (moveList[moveNum - 1][0] == NULLCHAR) {
4735 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4737 DisplayError(str, 0);
4739 if (first.sendTime) {
4740 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4742 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4743 if (firstMove && !bookHit) {
4745 if (first.useColors) {
4746 SendToProgram(gameMode == IcsPlayingWhite ?
4748 "black\ngo\n", &first);
4750 SendToProgram("go\n", &first);
4752 first.maybeThinking = TRUE;
4755 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4756 if (moveList[moveNum - 1][0] == NULLCHAR) {
4757 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4758 DisplayError(str, 0);
4760 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4761 SendMoveToProgram(moveNum - 1, &first);
4768 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4769 /* If move comes from a remote source, animate it. If it
4770 isn't remote, it will have already been animated. */
4771 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4772 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4774 if (!pausing && appData.highlightLastMove) {
4775 SetHighlights(fromX, fromY, toX, toY);
4779 /* Start the clocks */
4780 whiteFlag = blackFlag = FALSE;
4781 appData.clockMode = !(basetime == 0 && increment == 0);
4783 ics_clock_paused = TRUE;
4785 } else if (ticking == 1) {
4786 ics_clock_paused = FALSE;
4788 if (gameMode == IcsIdle ||
4789 relation == RELATION_OBSERVING_STATIC ||
4790 relation == RELATION_EXAMINING ||
4792 DisplayBothClocks();
4796 /* Display opponents and material strengths */
4797 if (gameInfo.variant != VariantBughouse &&
4798 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4799 if (tinyLayout || smallLayout) {
4800 if(gameInfo.variant == VariantNormal)
4801 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4802 gameInfo.white, white_stren, gameInfo.black, black_stren,
4803 basetime, increment);
4805 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4806 gameInfo.white, white_stren, gameInfo.black, black_stren,
4807 basetime, increment, (int) gameInfo.variant);
4809 if(gameInfo.variant == VariantNormal)
4810 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4811 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4812 basetime, increment);
4814 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4815 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4816 basetime, increment, VariantName(gameInfo.variant));
4819 if (appData.debugMode) {
4820 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4825 /* Display the board */
4826 if (!pausing && !appData.noGUI) {
4828 if (appData.premove)
4830 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4831 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4832 ClearPremoveHighlights();
4834 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4835 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4836 DrawPosition(j, boards[currentMove]);
4838 DisplayMove(moveNum - 1);
4839 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4840 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4841 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4842 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4846 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4848 if(bookHit) { // [HGM] book: simulate book reply
4849 static char bookMove[MSG_SIZ]; // a bit generous?
4851 programStats.nodes = programStats.depth = programStats.time =
4852 programStats.score = programStats.got_only_move = 0;
4853 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4855 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4856 strcat(bookMove, bookHit);
4857 HandleMachineMove(bookMove, &first);
4866 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4867 ics_getting_history = H_REQUESTED;
4868 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4874 AnalysisPeriodicEvent (int force)
4876 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4877 && !force) || !appData.periodicUpdates)
4880 /* Send . command to Crafty to collect stats */
4881 SendToProgram(".\n", &first);
4883 /* Don't send another until we get a response (this makes
4884 us stop sending to old Crafty's which don't understand
4885 the "." command (sending illegal cmds resets node count & time,
4886 which looks bad)) */
4887 programStats.ok_to_send = 0;
4891 ics_update_width (int new_width)
4893 ics_printf("set width %d\n", new_width);
4897 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4901 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4902 // null move in variant where engine does not understand it (for analysis purposes)
4903 SendBoard(cps, moveNum + 1); // send position after move in stead.
4906 if (cps->useUsermove) {
4907 SendToProgram("usermove ", cps);
4911 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4912 int len = space - parseList[moveNum];
4913 memcpy(buf, parseList[moveNum], len);
4915 buf[len] = NULLCHAR;
4917 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4919 SendToProgram(buf, cps);
4921 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4922 AlphaRank(moveList[moveNum], 4);
4923 SendToProgram(moveList[moveNum], cps);
4924 AlphaRank(moveList[moveNum], 4); // and back
4926 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4927 * the engine. It would be nice to have a better way to identify castle
4929 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4930 && cps->useOOCastle) {
4931 int fromX = moveList[moveNum][0] - AAA;
4932 int fromY = moveList[moveNum][1] - ONE;
4933 int toX = moveList[moveNum][2] - AAA;
4934 int toY = moveList[moveNum][3] - ONE;
4935 if((boards[moveNum][fromY][fromX] == WhiteKing
4936 && boards[moveNum][toY][toX] == WhiteRook)
4937 || (boards[moveNum][fromY][fromX] == BlackKing
4938 && boards[moveNum][toY][toX] == BlackRook)) {
4939 if(toX > fromX) SendToProgram("O-O\n", cps);
4940 else SendToProgram("O-O-O\n", cps);
4942 else SendToProgram(moveList[moveNum], cps);
4944 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4945 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4946 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4947 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4948 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4950 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4951 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952 SendToProgram(buf, cps);
4954 else SendToProgram(moveList[moveNum], cps);
4955 /* End of additions by Tord */
4958 /* [HGM] setting up the opening has brought engine in force mode! */
4959 /* Send 'go' if we are in a mode where machine should play. */
4960 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4961 (gameMode == TwoMachinesPlay ||
4963 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4965 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4966 SendToProgram("go\n", cps);
4967 if (appData.debugMode) {
4968 fprintf(debugFP, "(extra)\n");
4971 setboardSpoiledMachineBlack = 0;
4975 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4977 char user_move[MSG_SIZ];
4980 if(gameInfo.variant == VariantSChess && promoChar) {
4981 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4982 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4983 } else suffix[0] = NULLCHAR;
4987 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4988 (int)moveType, fromX, fromY, toX, toY);
4989 DisplayError(user_move + strlen("say "), 0);
4991 case WhiteKingSideCastle:
4992 case BlackKingSideCastle:
4993 case WhiteQueenSideCastleWild:
4994 case BlackQueenSideCastleWild:
4996 case WhiteHSideCastleFR:
4997 case BlackHSideCastleFR:
4999 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5001 case WhiteQueenSideCastle:
5002 case BlackQueenSideCastle:
5003 case WhiteKingSideCastleWild:
5004 case BlackKingSideCastleWild:
5006 case WhiteASideCastleFR:
5007 case BlackASideCastleFR:
5009 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5011 case WhiteNonPromotion:
5012 case BlackNonPromotion:
5013 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5015 case WhitePromotion:
5016 case BlackPromotion:
5017 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5018 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5019 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020 PieceToChar(WhiteFerz));
5021 else if(gameInfo.variant == VariantGreat)
5022 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5023 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5024 PieceToChar(WhiteMan));
5026 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5027 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5033 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5034 ToUpper(PieceToChar((ChessSquare) fromX)),
5035 AAA + toX, ONE + toY);
5037 case IllegalMove: /* could be a variant we don't quite understand */
5038 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5040 case WhiteCapturesEnPassant:
5041 case BlackCapturesEnPassant:
5042 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5043 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5046 SendToICS(user_move);
5047 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5048 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5053 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5054 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5055 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5056 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5057 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5060 if(gameMode != IcsExamining) { // is this ever not the case?
5061 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5063 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5064 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5065 } else { // on FICS we must first go to general examine mode
5066 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5068 if(gameInfo.variant != VariantNormal) {
5069 // try figure out wild number, as xboard names are not always valid on ICS
5070 for(i=1; i<=36; i++) {
5071 snprintf(buf, MSG_SIZ, "wild/%d", i);
5072 if(StringToVariant(buf) == gameInfo.variant) break;
5074 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5075 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5076 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5077 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5078 SendToICS(ics_prefix);
5080 if(startedFromSetupPosition || backwardMostMove != 0) {
5081 fen = PositionToFEN(backwardMostMove, NULL);
5082 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5083 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5085 } else { // FICS: everything has to set by separate bsetup commands
5086 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5087 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5089 if(!WhiteOnMove(backwardMostMove)) {
5090 SendToICS("bsetup tomove black\n");
5092 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5093 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5095 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5096 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5098 i = boards[backwardMostMove][EP_STATUS];
5099 if(i >= 0) { // set e.p.
5100 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5106 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5107 SendToICS("bsetup done\n"); // switch to normal examining.
5109 for(i = backwardMostMove; i<last; i++) {
5111 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5114 SendToICS(ics_prefix);
5115 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5119 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5121 if (rf == DROP_RANK) {
5122 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5123 sprintf(move, "%c@%c%c\n",
5124 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5126 if (promoChar == 'x' || promoChar == NULLCHAR) {
5127 sprintf(move, "%c%c%c%c\n",
5128 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5130 sprintf(move, "%c%c%c%c%c\n",
5131 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5137 ProcessICSInitScript (FILE *f)
5141 while (fgets(buf, MSG_SIZ, f)) {
5142 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5149 static int lastX, lastY, selectFlag, dragging;
5154 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5155 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5156 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5157 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5158 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5159 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5162 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5163 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5164 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5165 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5166 if(!step) step = -1;
5167 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5168 appData.testLegality && (promoSweep == king ||
5169 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5170 ChangeDragPiece(promoSweep);
5174 PromoScroll (int x, int y)
5178 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5179 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5180 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5181 if(!step) return FALSE;
5182 lastX = x; lastY = y;
5183 if((promoSweep < BlackPawn) == flipView) step = -step;
5184 if(step > 0) selectFlag = 1;
5185 if(!selectFlag) Sweep(step);
5190 NextPiece (int step)
5192 ChessSquare piece = boards[currentMove][toY][toX];
5195 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5196 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5197 if(!step) step = -1;
5198 } while(PieceToChar(pieceSweep) == '.');
5199 boards[currentMove][toY][toX] = pieceSweep;
5200 DrawPosition(FALSE, boards[currentMove]);
5201 boards[currentMove][toY][toX] = piece;
5203 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5205 AlphaRank (char *move, int n)
5207 // char *p = move, c; int x, y;
5209 if (appData.debugMode) {
5210 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5214 move[2]>='0' && move[2]<='9' &&
5215 move[3]>='a' && move[3]<='x' ) {
5217 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5218 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5220 if(move[0]>='0' && move[0]<='9' &&
5221 move[1]>='a' && move[1]<='x' &&
5222 move[2]>='0' && move[2]<='9' &&
5223 move[3]>='a' && move[3]<='x' ) {
5224 /* input move, Shogi -> normal */
5225 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5226 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5227 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5228 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5231 move[3]>='0' && move[3]<='9' &&
5232 move[2]>='a' && move[2]<='x' ) {
5234 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5235 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5238 move[0]>='a' && move[0]<='x' &&
5239 move[3]>='0' && move[3]<='9' &&
5240 move[2]>='a' && move[2]<='x' ) {
5241 /* output move, normal -> Shogi */
5242 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5243 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5244 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5245 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5246 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5248 if (appData.debugMode) {
5249 fprintf(debugFP, " out = '%s'\n", move);
5253 char yy_textstr[8000];
5255 /* Parser for moves from gnuchess, ICS, or user typein box */
5257 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5259 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5261 switch (*moveType) {
5262 case WhitePromotion:
5263 case BlackPromotion:
5264 case WhiteNonPromotion:
5265 case BlackNonPromotion:
5267 case WhiteCapturesEnPassant:
5268 case BlackCapturesEnPassant:
5269 case WhiteKingSideCastle:
5270 case WhiteQueenSideCastle:
5271 case BlackKingSideCastle:
5272 case BlackQueenSideCastle:
5273 case WhiteKingSideCastleWild:
5274 case WhiteQueenSideCastleWild:
5275 case BlackKingSideCastleWild:
5276 case BlackQueenSideCastleWild:
5277 /* Code added by Tord: */
5278 case WhiteHSideCastleFR:
5279 case WhiteASideCastleFR:
5280 case BlackHSideCastleFR:
5281 case BlackASideCastleFR:
5282 /* End of code added by Tord */
5283 case IllegalMove: /* bug or odd chess variant */
5284 *fromX = currentMoveString[0] - AAA;
5285 *fromY = currentMoveString[1] - ONE;
5286 *toX = currentMoveString[2] - AAA;
5287 *toY = currentMoveString[3] - ONE;
5288 *promoChar = currentMoveString[4];
5289 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5290 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5291 if (appData.debugMode) {
5292 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5294 *fromX = *fromY = *toX = *toY = 0;
5297 if (appData.testLegality) {
5298 return (*moveType != IllegalMove);
5300 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5301 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5306 *fromX = *moveType == WhiteDrop ?
5307 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5308 (int) CharToPiece(ToLower(currentMoveString[0]));
5310 *toX = currentMoveString[2] - AAA;
5311 *toY = currentMoveString[3] - ONE;
5312 *promoChar = NULLCHAR;
5316 case ImpossibleMove:
5326 if (appData.debugMode) {
5327 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5330 *fromX = *fromY = *toX = *toY = 0;
5331 *promoChar = NULLCHAR;
5336 Boolean pushed = FALSE;
5337 char *lastParseAttempt;
5340 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5341 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5342 int fromX, fromY, toX, toY; char promoChar;
5347 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5348 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5351 endPV = forwardMostMove;
5353 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5354 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5355 lastParseAttempt = pv;
5356 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5357 if(!valid && nr == 0 &&
5358 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5359 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5360 // Hande case where played move is different from leading PV move
5361 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5362 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5363 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5364 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5365 endPV += 2; // if position different, keep this
5366 moveList[endPV-1][0] = fromX + AAA;
5367 moveList[endPV-1][1] = fromY + ONE;
5368 moveList[endPV-1][2] = toX + AAA;
5369 moveList[endPV-1][3] = toY + ONE;
5370 parseList[endPV-1][0] = NULLCHAR;
5371 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5374 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5375 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5376 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5377 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5378 valid++; // allow comments in PV
5382 if(endPV+1 > framePtr) break; // no space, truncate
5385 CopyBoard(boards[endPV], boards[endPV-1]);
5386 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5387 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5388 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5389 CoordsToAlgebraic(boards[endPV - 1],
5390 PosFlags(endPV - 1),
5391 fromY, fromX, toY, toX, promoChar,
5392 parseList[endPV - 1]);
5394 if(atEnd == 2) return; // used hidden, for PV conversion
5395 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5396 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5397 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5398 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5399 DrawPosition(TRUE, boards[currentMove]);
5403 MultiPV (ChessProgramState *cps)
5404 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5406 for(i=0; i<cps->nrOptions; i++)
5407 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5413 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5415 int startPV, multi, lineStart, origIndex = index;
5416 char *p, buf2[MSG_SIZ];
5418 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5419 lastX = x; lastY = y;
5420 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5421 lineStart = startPV = index;
5422 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5423 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5425 do{ while(buf[index] && buf[index] != '\n') index++;
5426 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5428 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5429 int n = first.option[multi].value;
5430 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5431 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5432 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5433 first.option[multi].value = n;
5436 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5437 ExcludeClick(origIndex - lineStart);
5440 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5441 *start = startPV; *end = index-1;
5448 static char buf[10*MSG_SIZ];
5449 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5451 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5452 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5453 for(i = forwardMostMove; i<endPV; i++){
5454 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5455 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5458 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5459 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5465 LoadPV (int x, int y)
5466 { // called on right mouse click to load PV
5467 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5468 lastX = x; lastY = y;
5469 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5476 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5477 if(endPV < 0) return;
5478 if(appData.autoCopyPV) CopyFENToClipboard();
5480 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5481 Boolean saveAnimate = appData.animate;
5483 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5484 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5485 } else storedGames--; // abandon shelved tail of original game
5488 forwardMostMove = currentMove;
5489 currentMove = oldFMM;
5490 appData.animate = FALSE;
5491 ToNrEvent(forwardMostMove);
5492 appData.animate = saveAnimate;
5494 currentMove = forwardMostMove;
5495 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5496 ClearPremoveHighlights();
5497 DrawPosition(TRUE, boards[currentMove]);
5501 MovePV (int x, int y, int h)
5502 { // step through PV based on mouse coordinates (called on mouse move)
5503 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5505 // we must somehow check if right button is still down (might be released off board!)
5506 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5507 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5508 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5510 lastX = x; lastY = y;
5512 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5513 if(endPV < 0) return;
5514 if(y < margin) step = 1; else
5515 if(y > h - margin) step = -1;
5516 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5517 currentMove += step;
5518 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5519 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5520 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5521 DrawPosition(FALSE, boards[currentMove]);
5525 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5526 // All positions will have equal probability, but the current method will not provide a unique
5527 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5533 int piecesLeft[(int)BlackPawn];
5534 int seed, nrOfShuffles;
5537 GetPositionNumber ()
5538 { // sets global variable seed
5541 seed = appData.defaultFrcPosition;
5542 if(seed < 0) { // randomize based on time for negative FRC position numbers
5543 for(i=0; i<50; i++) seed += random();
5544 seed = random() ^ random() >> 8 ^ random() << 8;
5545 if(seed<0) seed = -seed;
5550 put (Board board, int pieceType, int rank, int n, int shade)
5551 // put the piece on the (n-1)-th empty squares of the given shade
5555 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5556 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5557 board[rank][i] = (ChessSquare) pieceType;
5558 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5560 piecesLeft[pieceType]--;
5569 AddOnePiece (Board board, int pieceType, int rank, int shade)
5570 // calculate where the next piece goes, (any empty square), and put it there
5574 i = seed % squaresLeft[shade];
5575 nrOfShuffles *= squaresLeft[shade];
5576 seed /= squaresLeft[shade];
5577 put(board, pieceType, rank, i, shade);
5581 AddTwoPieces (Board board, int pieceType, int rank)
5582 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5584 int i, n=squaresLeft[ANY], j=n-1, k;
5586 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5587 i = seed % k; // pick one
5590 while(i >= j) i -= j--;
5591 j = n - 1 - j; i += j;
5592 put(board, pieceType, rank, j, ANY);
5593 put(board, pieceType, rank, i, ANY);
5597 SetUpShuffle (Board board, int number)
5601 GetPositionNumber(); nrOfShuffles = 1;
5603 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5604 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5605 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5607 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5609 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5610 p = (int) board[0][i];
5611 if(p < (int) BlackPawn) piecesLeft[p] ++;
5612 board[0][i] = EmptySquare;
5615 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5616 // shuffles restricted to allow normal castling put KRR first
5617 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5618 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5619 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5620 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5621 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5622 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5623 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5624 put(board, WhiteRook, 0, 0, ANY);
5625 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5628 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5629 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5630 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5631 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5632 while(piecesLeft[p] >= 2) {
5633 AddOnePiece(board, p, 0, LITE);
5634 AddOnePiece(board, p, 0, DARK);
5636 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5639 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5640 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5641 // but we leave King and Rooks for last, to possibly obey FRC restriction
5642 if(p == (int)WhiteRook) continue;
5643 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5644 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5647 // now everything is placed, except perhaps King (Unicorn) and Rooks
5649 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5650 // Last King gets castling rights
5651 while(piecesLeft[(int)WhiteUnicorn]) {
5652 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5653 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5656 while(piecesLeft[(int)WhiteKing]) {
5657 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5658 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5663 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5664 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5667 // Only Rooks can be left; simply place them all
5668 while(piecesLeft[(int)WhiteRook]) {
5669 i = put(board, WhiteRook, 0, 0, ANY);
5670 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5673 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5675 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5678 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5679 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5682 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5686 SetCharTable (char *table, const char * map)
5687 /* [HGM] moved here from winboard.c because of its general usefulness */
5688 /* Basically a safe strcpy that uses the last character as King */
5690 int result = FALSE; int NrPieces;
5692 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5693 && NrPieces >= 12 && !(NrPieces&1)) {
5694 int i; /* [HGM] Accept even length from 12 to 34 */
5696 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5697 for( i=0; i<NrPieces/2-1; i++ ) {
5699 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5701 table[(int) WhiteKing] = map[NrPieces/2-1];
5702 table[(int) BlackKing] = map[NrPieces-1];
5711 Prelude (Board board)
5712 { // [HGM] superchess: random selection of exo-pieces
5713 int i, j, k; ChessSquare p;
5714 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5716 GetPositionNumber(); // use FRC position number
5718 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5719 SetCharTable(pieceToChar, appData.pieceToCharTable);
5720 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5721 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5724 j = seed%4; seed /= 4;
5725 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5726 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5727 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5728 j = seed%3 + (seed%3 >= j); seed /= 3;
5729 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5730 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5731 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5732 j = seed%3; seed /= 3;
5733 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5734 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5735 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5736 j = seed%2 + (seed%2 >= j); seed /= 2;
5737 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5738 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5739 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5740 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5741 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5742 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5743 put(board, exoPieces[0], 0, 0, ANY);
5744 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5748 InitPosition (int redraw)
5750 ChessSquare (* pieces)[BOARD_FILES];
5751 int i, j, pawnRow, overrule,
5752 oldx = gameInfo.boardWidth,
5753 oldy = gameInfo.boardHeight,
5754 oldh = gameInfo.holdingsWidth;
5757 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5759 /* [AS] Initialize pv info list [HGM] and game status */
5761 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5762 pvInfoList[i].depth = 0;
5763 boards[i][EP_STATUS] = EP_NONE;
5764 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5767 initialRulePlies = 0; /* 50-move counter start */
5769 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5770 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5774 /* [HGM] logic here is completely changed. In stead of full positions */
5775 /* the initialized data only consist of the two backranks. The switch */
5776 /* selects which one we will use, which is than copied to the Board */
5777 /* initialPosition, which for the rest is initialized by Pawns and */
5778 /* empty squares. This initial position is then copied to boards[0], */
5779 /* possibly after shuffling, so that it remains available. */
5781 gameInfo.holdingsWidth = 0; /* default board sizes */
5782 gameInfo.boardWidth = 8;
5783 gameInfo.boardHeight = 8;
5784 gameInfo.holdingsSize = 0;
5785 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5786 for(i=0; i<BOARD_FILES-2; i++)
5787 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5788 initialPosition[EP_STATUS] = EP_NONE;
5789 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5790 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5791 SetCharTable(pieceNickName, appData.pieceNickNames);
5792 else SetCharTable(pieceNickName, "............");
5795 switch (gameInfo.variant) {
5796 case VariantFischeRandom:
5797 shuffleOpenings = TRUE;
5800 case VariantShatranj:
5801 pieces = ShatranjArray;
5802 nrCastlingRights = 0;
5803 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5806 pieces = makrukArray;
5807 nrCastlingRights = 0;
5808 startedFromSetupPosition = TRUE;
5809 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5811 case VariantTwoKings:
5812 pieces = twoKingsArray;
5815 pieces = GrandArray;
5816 nrCastlingRights = 0;
5817 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5818 gameInfo.boardWidth = 10;
5819 gameInfo.boardHeight = 10;
5820 gameInfo.holdingsSize = 7;
5822 case VariantCapaRandom:
5823 shuffleOpenings = TRUE;
5824 case VariantCapablanca:
5825 pieces = CapablancaArray;
5826 gameInfo.boardWidth = 10;
5827 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5830 pieces = GothicArray;
5831 gameInfo.boardWidth = 10;
5832 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5835 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5836 gameInfo.holdingsSize = 7;
5839 pieces = JanusArray;
5840 gameInfo.boardWidth = 10;
5841 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5842 nrCastlingRights = 6;
5843 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5844 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5845 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5846 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5847 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5848 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5851 pieces = FalconArray;
5852 gameInfo.boardWidth = 10;
5853 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5855 case VariantXiangqi:
5856 pieces = XiangqiArray;
5857 gameInfo.boardWidth = 9;
5858 gameInfo.boardHeight = 10;
5859 nrCastlingRights = 0;
5860 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5863 pieces = ShogiArray;
5864 gameInfo.boardWidth = 9;
5865 gameInfo.boardHeight = 9;
5866 gameInfo.holdingsSize = 7;
5867 nrCastlingRights = 0;
5868 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5870 case VariantCourier:
5871 pieces = CourierArray;
5872 gameInfo.boardWidth = 12;
5873 nrCastlingRights = 0;
5874 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5876 case VariantKnightmate:
5877 pieces = KnightmateArray;
5878 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5880 case VariantSpartan:
5881 pieces = SpartanArray;
5882 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5885 pieces = fairyArray;
5886 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5889 pieces = GreatArray;
5890 gameInfo.boardWidth = 10;
5891 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5892 gameInfo.holdingsSize = 8;
5896 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5897 gameInfo.holdingsSize = 8;
5898 startedFromSetupPosition = TRUE;
5900 case VariantCrazyhouse:
5901 case VariantBughouse:
5903 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5904 gameInfo.holdingsSize = 5;
5906 case VariantWildCastle:
5908 /* !!?shuffle with kings guaranteed to be on d or e file */
5909 shuffleOpenings = 1;
5911 case VariantNoCastle:
5913 nrCastlingRights = 0;
5914 /* !!?unconstrained back-rank shuffle */
5915 shuffleOpenings = 1;
5920 if(appData.NrFiles >= 0) {
5921 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5922 gameInfo.boardWidth = appData.NrFiles;
5924 if(appData.NrRanks >= 0) {
5925 gameInfo.boardHeight = appData.NrRanks;
5927 if(appData.holdingsSize >= 0) {
5928 i = appData.holdingsSize;
5929 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5930 gameInfo.holdingsSize = i;
5932 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5933 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5934 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5936 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5937 if(pawnRow < 1) pawnRow = 1;
5938 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5940 /* User pieceToChar list overrules defaults */
5941 if(appData.pieceToCharTable != NULL)
5942 SetCharTable(pieceToChar, appData.pieceToCharTable);
5944 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5946 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5947 s = (ChessSquare) 0; /* account holding counts in guard band */
5948 for( i=0; i<BOARD_HEIGHT; i++ )
5949 initialPosition[i][j] = s;
5951 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5952 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5953 initialPosition[pawnRow][j] = WhitePawn;
5954 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5955 if(gameInfo.variant == VariantXiangqi) {
5957 initialPosition[pawnRow][j] =
5958 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5959 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5960 initialPosition[2][j] = WhiteCannon;
5961 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5965 if(gameInfo.variant == VariantGrand) {
5966 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5967 initialPosition[0][j] = WhiteRook;
5968 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5971 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5973 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5976 initialPosition[1][j] = WhiteBishop;
5977 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5979 initialPosition[1][j] = WhiteRook;
5980 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5983 if( nrCastlingRights == -1) {
5984 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5985 /* This sets default castling rights from none to normal corners */
5986 /* Variants with other castling rights must set them themselves above */
5987 nrCastlingRights = 6;
5989 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5990 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5991 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5992 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5993 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5994 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5997 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5998 if(gameInfo.variant == VariantGreat) { // promotion commoners
5999 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6000 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6001 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6002 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6004 if( gameInfo.variant == VariantSChess ) {
6005 initialPosition[1][0] = BlackMarshall;
6006 initialPosition[2][0] = BlackAngel;
6007 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6008 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6009 initialPosition[1][1] = initialPosition[2][1] =
6010 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6012 if (appData.debugMode) {
6013 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6015 if(shuffleOpenings) {
6016 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6017 startedFromSetupPosition = TRUE;
6019 if(startedFromPositionFile) {
6020 /* [HGM] loadPos: use PositionFile for every new game */
6021 CopyBoard(initialPosition, filePosition);
6022 for(i=0; i<nrCastlingRights; i++)
6023 initialRights[i] = filePosition[CASTLING][i];
6024 startedFromSetupPosition = TRUE;
6027 CopyBoard(boards[0], initialPosition);
6029 if(oldx != gameInfo.boardWidth ||
6030 oldy != gameInfo.boardHeight ||
6031 oldv != gameInfo.variant ||
6032 oldh != gameInfo.holdingsWidth
6034 InitDrawingSizes(-2 ,0);
6036 oldv = gameInfo.variant;
6038 DrawPosition(TRUE, boards[currentMove]);
6042 SendBoard (ChessProgramState *cps, int moveNum)
6044 char message[MSG_SIZ];
6046 if (cps->useSetboard) {
6047 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6048 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6049 SendToProgram(message, cps);
6054 int i, j, left=0, right=BOARD_WIDTH;
6055 /* Kludge to set black to move, avoiding the troublesome and now
6056 * deprecated "black" command.
6058 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6059 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6061 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6063 SendToProgram("edit\n", cps);
6064 SendToProgram("#\n", cps);
6065 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6066 bp = &boards[moveNum][i][left];
6067 for (j = left; j < right; j++, bp++) {
6068 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6069 if ((int) *bp < (int) BlackPawn) {
6070 if(j == BOARD_RGHT+1)
6071 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6072 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6073 if(message[0] == '+' || message[0] == '~') {
6074 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6075 PieceToChar((ChessSquare)(DEMOTED *bp)),
6078 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6079 message[1] = BOARD_RGHT - 1 - j + '1';
6080 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082 SendToProgram(message, cps);
6087 SendToProgram("c\n", cps);
6088 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6089 bp = &boards[moveNum][i][left];
6090 for (j = left; j < right; j++, bp++) {
6091 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6092 if (((int) *bp != (int) EmptySquare)
6093 && ((int) *bp >= (int) BlackPawn)) {
6094 if(j == BOARD_LEFT-2)
6095 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6096 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6098 if(message[0] == '+' || message[0] == '~') {
6099 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6100 PieceToChar((ChessSquare)(DEMOTED *bp)),
6103 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6104 message[1] = BOARD_RGHT - 1 - j + '1';
6105 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6107 SendToProgram(message, cps);
6112 SendToProgram(".\n", cps);
6114 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6117 char exclusionHeader[MSG_SIZ];
6118 int exCnt, excludePtr;
6119 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6120 static Exclusion excluTab[200];
6121 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6127 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6128 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6135 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6136 excludePtr = 24; exCnt = 0;
6141 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6142 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6143 char buf[2*MOVE_LEN], *p;
6144 Exclusion *e = excluTab;
6146 for(i=0; i<exCnt; i++)
6147 if(e[i].ff == fromX && e[i].fr == fromY &&
6148 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6149 if(i == exCnt) { // was not in exclude list; add it
6150 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6151 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6152 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6155 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6156 excludePtr++; e[i].mark = excludePtr++;
6157 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6160 exclusionHeader[e[i].mark] = state;
6164 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6165 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6166 char *p, buf[MSG_SIZ];
6169 if(promoChar == -1) { // kludge to indicate best move
6170 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6171 return 1; // if unparsable, abort
6173 // update exclusion map (resolving toggle by consulting existing state)
6174 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6176 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6177 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6178 excludeMap[k] |= 1<<j;
6179 else excludeMap[k] &= ~(1<<j);
6181 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6183 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6184 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6185 SendToProgram(buf, &first);
6186 return (state == '+');
6190 ExcludeClick (int index)
6194 Exclusion *e = excluTab;
6195 if(index < 25) { // none, best or tail clicked
6196 if(index < 13) { // none: include all
6197 WriteMap(0); // clear map
6198 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6199 SendToProgram("include all\n", &first); // and inform engine
6200 } else if(index > 18) { // tail
6201 if(exclusionHeader[19] == '-') { // tail was excluded
6202 SendToProgram("include all\n", &first);
6203 WriteMap(0); // clear map completely
6204 // now re-exclude selected moves
6205 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6206 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6207 } else { // tail was included or in mixed state
6208 SendToProgram("exclude all\n", &first);
6209 WriteMap(0xFF); // fill map completely
6210 // now re-include selected moves
6211 j = 0; // count them
6212 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6213 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6214 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6217 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6220 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6221 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6222 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6229 DefaultPromoChoice (int white)
6232 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6233 result = WhiteFerz; // no choice
6234 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6235 result= WhiteKing; // in Suicide Q is the last thing we want
6236 else if(gameInfo.variant == VariantSpartan)
6237 result = white ? WhiteQueen : WhiteAngel;
6238 else result = WhiteQueen;
6239 if(!white) result = WHITE_TO_BLACK result;
6243 static int autoQueen; // [HGM] oneclick
6246 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6248 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6249 /* [HGM] add Shogi promotions */
6250 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6255 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6256 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6258 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6259 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6262 piece = boards[currentMove][fromY][fromX];
6263 if(gameInfo.variant == VariantShogi) {
6264 promotionZoneSize = BOARD_HEIGHT/3;
6265 highestPromotingPiece = (int)WhiteFerz;
6266 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6267 promotionZoneSize = 3;
6270 // Treat Lance as Pawn when it is not representing Amazon
6271 if(gameInfo.variant != VariantSuper) {
6272 if(piece == WhiteLance) piece = WhitePawn; else
6273 if(piece == BlackLance) piece = BlackPawn;
6276 // next weed out all moves that do not touch the promotion zone at all
6277 if((int)piece >= BlackPawn) {
6278 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6280 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6282 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6283 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6286 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6288 // weed out mandatory Shogi promotions
6289 if(gameInfo.variant == VariantShogi) {
6290 if(piece >= BlackPawn) {
6291 if(toY == 0 && piece == BlackPawn ||
6292 toY == 0 && piece == BlackQueen ||
6293 toY <= 1 && piece == BlackKnight) {
6298 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6299 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6300 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6307 // weed out obviously illegal Pawn moves
6308 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6309 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6310 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6311 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6312 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6313 // note we are not allowed to test for valid (non-)capture, due to premove
6316 // we either have a choice what to promote to, or (in Shogi) whether to promote
6317 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6318 *promoChoice = PieceToChar(BlackFerz); // no choice
6321 // no sense asking what we must promote to if it is going to explode...
6322 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6323 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6326 // give caller the default choice even if we will not make it
6327 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6328 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6329 if( sweepSelect && gameInfo.variant != VariantGreat
6330 && gameInfo.variant != VariantGrand
6331 && gameInfo.variant != VariantSuper) return FALSE;
6332 if(autoQueen) return FALSE; // predetermined
6334 // suppress promotion popup on illegal moves that are not premoves
6335 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6336 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6337 if(appData.testLegality && !premove) {
6338 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6339 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6340 if(moveType != WhitePromotion && moveType != BlackPromotion)
6348 InPalace (int row, int column)
6349 { /* [HGM] for Xiangqi */
6350 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6351 column < (BOARD_WIDTH + 4)/2 &&
6352 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6357 PieceForSquare (int x, int y)
6359 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6362 return boards[currentMove][y][x];
6366 OKToStartUserMove (int x, int y)
6368 ChessSquare from_piece;
6371 if (matchMode) return FALSE;
6372 if (gameMode == EditPosition) return TRUE;
6374 if (x >= 0 && y >= 0)
6375 from_piece = boards[currentMove][y][x];
6377 from_piece = EmptySquare;
6379 if (from_piece == EmptySquare) return FALSE;
6381 white_piece = (int)from_piece >= (int)WhitePawn &&
6382 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6386 case TwoMachinesPlay:
6394 case MachinePlaysWhite:
6395 case IcsPlayingBlack:
6396 if (appData.zippyPlay) return FALSE;
6398 DisplayMoveError(_("You are playing Black"));
6403 case MachinePlaysBlack:
6404 case IcsPlayingWhite:
6405 if (appData.zippyPlay) return FALSE;
6407 DisplayMoveError(_("You are playing White"));
6412 case PlayFromGameFile:
6413 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6415 if (!white_piece && WhiteOnMove(currentMove)) {
6416 DisplayMoveError(_("It is White's turn"));
6419 if (white_piece && !WhiteOnMove(currentMove)) {
6420 DisplayMoveError(_("It is Black's turn"));
6423 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6424 /* Editing correspondence game history */
6425 /* Could disallow this or prompt for confirmation */
6430 case BeginningOfGame:
6431 if (appData.icsActive) return FALSE;
6432 if (!appData.noChessProgram) {
6434 DisplayMoveError(_("You are playing White"));
6441 if (!white_piece && WhiteOnMove(currentMove)) {
6442 DisplayMoveError(_("It is White's turn"));
6445 if (white_piece && !WhiteOnMove(currentMove)) {
6446 DisplayMoveError(_("It is Black's turn"));
6455 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6456 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6457 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6458 && gameMode != AnalyzeFile && gameMode != Training) {
6459 DisplayMoveError(_("Displayed position is not current"));
6466 OnlyMove (int *x, int *y, Boolean captures)
6468 DisambiguateClosure cl;
6469 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6471 case MachinePlaysBlack:
6472 case IcsPlayingWhite:
6473 case BeginningOfGame:
6474 if(!WhiteOnMove(currentMove)) return FALSE;
6476 case MachinePlaysWhite:
6477 case IcsPlayingBlack:
6478 if(WhiteOnMove(currentMove)) return FALSE;
6485 cl.pieceIn = EmptySquare;
6490 cl.promoCharIn = NULLCHAR;
6491 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6492 if( cl.kind == NormalMove ||
6493 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6494 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6495 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6502 if(cl.kind != ImpossibleMove) return FALSE;
6503 cl.pieceIn = EmptySquare;
6508 cl.promoCharIn = NULLCHAR;
6509 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6510 if( cl.kind == NormalMove ||
6511 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6512 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6513 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6518 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6524 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6525 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6526 int lastLoadGameUseList = FALSE;
6527 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6528 ChessMove lastLoadGameStart = EndOfFile;
6532 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6535 ChessSquare pdown, pup;
6536 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6539 /* Check if the user is playing in turn. This is complicated because we
6540 let the user "pick up" a piece before it is his turn. So the piece he
6541 tried to pick up may have been captured by the time he puts it down!
6542 Therefore we use the color the user is supposed to be playing in this
6543 test, not the color of the piece that is currently on the starting
6544 square---except in EditGame mode, where the user is playing both
6545 sides; fortunately there the capture race can't happen. (It can
6546 now happen in IcsExamining mode, but that's just too bad. The user
6547 will get a somewhat confusing message in that case.)
6552 case TwoMachinesPlay:
6556 /* We switched into a game mode where moves are not accepted,
6557 perhaps while the mouse button was down. */
6560 case MachinePlaysWhite:
6561 /* User is moving for Black */
6562 if (WhiteOnMove(currentMove)) {
6563 DisplayMoveError(_("It is White's turn"));
6568 case MachinePlaysBlack:
6569 /* User is moving for White */
6570 if (!WhiteOnMove(currentMove)) {
6571 DisplayMoveError(_("It is Black's turn"));
6576 case PlayFromGameFile:
6577 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6580 case BeginningOfGame:
6583 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6584 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6585 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6586 /* User is moving for Black */
6587 if (WhiteOnMove(currentMove)) {
6588 DisplayMoveError(_("It is White's turn"));
6592 /* User is moving for White */
6593 if (!WhiteOnMove(currentMove)) {
6594 DisplayMoveError(_("It is Black's turn"));
6600 case IcsPlayingBlack:
6601 /* User is moving for Black */
6602 if (WhiteOnMove(currentMove)) {
6603 if (!appData.premove) {
6604 DisplayMoveError(_("It is White's turn"));
6605 } else if (toX >= 0 && toY >= 0) {
6608 premoveFromX = fromX;
6609 premoveFromY = fromY;
6610 premovePromoChar = promoChar;
6612 if (appData.debugMode)
6613 fprintf(debugFP, "Got premove: fromX %d,"
6614 "fromY %d, toX %d, toY %d\n",
6615 fromX, fromY, toX, toY);
6621 case IcsPlayingWhite:
6622 /* User is moving for White */
6623 if (!WhiteOnMove(currentMove)) {
6624 if (!appData.premove) {
6625 DisplayMoveError(_("It is Black's turn"));
6626 } else if (toX >= 0 && toY >= 0) {
6629 premoveFromX = fromX;
6630 premoveFromY = fromY;
6631 premovePromoChar = promoChar;
6633 if (appData.debugMode)
6634 fprintf(debugFP, "Got premove: fromX %d,"
6635 "fromY %d, toX %d, toY %d\n",
6636 fromX, fromY, toX, toY);
6646 /* EditPosition, empty square, or different color piece;
6647 click-click move is possible */
6648 if (toX == -2 || toY == -2) {
6649 boards[0][fromY][fromX] = EmptySquare;
6650 DrawPosition(FALSE, boards[currentMove]);
6652 } else if (toX >= 0 && toY >= 0) {
6653 boards[0][toY][toX] = boards[0][fromY][fromX];
6654 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6655 if(boards[0][fromY][0] != EmptySquare) {
6656 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6657 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6660 if(fromX == BOARD_RGHT+1) {
6661 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6662 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6663 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6666 boards[0][fromY][fromX] = EmptySquare;
6667 DrawPosition(FALSE, boards[currentMove]);
6673 if(toX < 0 || toY < 0) return;
6674 pdown = boards[currentMove][fromY][fromX];
6675 pup = boards[currentMove][toY][toX];
6677 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6678 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6679 if( pup != EmptySquare ) return;
6680 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6681 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6682 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6683 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6684 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6685 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6686 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6690 /* [HGM] always test for legality, to get promotion info */
6691 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6692 fromY, fromX, toY, toX, promoChar);
6694 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6696 /* [HGM] but possibly ignore an IllegalMove result */
6697 if (appData.testLegality) {
6698 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6699 DisplayMoveError(_("Illegal move"));
6704 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6705 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6706 ClearPremoveHighlights(); // was included
6707 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6711 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6714 /* Common tail of UserMoveEvent and DropMenuEvent */
6716 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6720 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6721 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6722 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6723 if(WhiteOnMove(currentMove)) {
6724 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6726 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6730 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6731 move type in caller when we know the move is a legal promotion */
6732 if(moveType == NormalMove && promoChar)
6733 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6735 /* [HGM] <popupFix> The following if has been moved here from
6736 UserMoveEvent(). Because it seemed to belong here (why not allow
6737 piece drops in training games?), and because it can only be
6738 performed after it is known to what we promote. */
6739 if (gameMode == Training) {
6740 /* compare the move played on the board to the next move in the
6741 * game. If they match, display the move and the opponent's response.
6742 * If they don't match, display an error message.
6746 CopyBoard(testBoard, boards[currentMove]);
6747 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6749 if (CompareBoards(testBoard, boards[currentMove+1])) {
6750 ForwardInner(currentMove+1);
6752 /* Autoplay the opponent's response.
6753 * if appData.animate was TRUE when Training mode was entered,
6754 * the response will be animated.
6756 saveAnimate = appData.animate;
6757 appData.animate = animateTraining;
6758 ForwardInner(currentMove+1);
6759 appData.animate = saveAnimate;
6761 /* check for the end of the game */
6762 if (currentMove >= forwardMostMove) {
6763 gameMode = PlayFromGameFile;
6765 SetTrainingModeOff();
6766 DisplayInformation(_("End of game"));
6769 DisplayError(_("Incorrect move"), 0);
6774 /* Ok, now we know that the move is good, so we can kill
6775 the previous line in Analysis Mode */
6776 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6777 && currentMove < forwardMostMove) {
6778 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6779 else forwardMostMove = currentMove;
6784 /* If we need the chess program but it's dead, restart it */
6785 ResurrectChessProgram();
6787 /* A user move restarts a paused game*/
6791 thinkOutput[0] = NULLCHAR;
6793 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6795 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6796 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6800 if (gameMode == BeginningOfGame) {
6801 if (appData.noChessProgram) {
6802 gameMode = EditGame;
6806 gameMode = MachinePlaysBlack;
6809 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6811 if (first.sendName) {
6812 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6813 SendToProgram(buf, &first);
6820 /* Relay move to ICS or chess engine */
6821 if (appData.icsActive) {
6822 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6823 gameMode == IcsExamining) {
6824 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6825 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6827 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829 // also send plain move, in case ICS does not understand atomic claims
6830 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6834 if (first.sendTime && (gameMode == BeginningOfGame ||
6835 gameMode == MachinePlaysWhite ||
6836 gameMode == MachinePlaysBlack)) {
6837 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6839 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6840 // [HGM] book: if program might be playing, let it use book
6841 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6842 first.maybeThinking = TRUE;
6843 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6844 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6845 SendBoard(&first, currentMove+1);
6846 } else SendMoveToProgram(forwardMostMove-1, &first);
6847 if (currentMove == cmailOldMove + 1) {
6848 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6852 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6856 if(appData.testLegality)
6857 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6863 if (WhiteOnMove(currentMove)) {
6864 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6866 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6870 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6875 case MachinePlaysBlack:
6876 case MachinePlaysWhite:
6877 /* disable certain menu options while machine is thinking */
6878 SetMachineThinkingEnables();
6885 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6886 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6888 if(bookHit) { // [HGM] book: simulate book reply
6889 static char bookMove[MSG_SIZ]; // a bit generous?
6891 programStats.nodes = programStats.depth = programStats.time =
6892 programStats.score = programStats.got_only_move = 0;
6893 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6895 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6896 strcat(bookMove, bookHit);
6897 HandleMachineMove(bookMove, &first);
6903 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6905 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6906 Markers *m = (Markers *) closure;
6907 if(rf == fromY && ff == fromX)
6908 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6909 || kind == WhiteCapturesEnPassant
6910 || kind == BlackCapturesEnPassant);
6911 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6915 MarkTargetSquares (int clear)
6918 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6919 !appData.testLegality || gameMode == EditPosition) return;
6921 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6924 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6925 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6926 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6928 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6931 DrawPosition(TRUE, NULL);
6935 Explode (Board board, int fromX, int fromY, int toX, int toY)
6937 if(gameInfo.variant == VariantAtomic &&
6938 (board[toY][toX] != EmptySquare || // capture?
6939 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6940 board[fromY][fromX] == BlackPawn )
6942 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6948 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6951 CanPromote (ChessSquare piece, int y)
6953 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6954 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6955 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6956 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6957 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6958 gameInfo.variant == VariantMakruk) return FALSE;
6959 return (piece == BlackPawn && y == 1 ||
6960 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6961 piece == BlackLance && y == 1 ||
6962 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6966 LeftClick (ClickType clickType, int xPix, int yPix)
6969 Boolean saveAnimate;
6970 static int second = 0, promotionChoice = 0, clearFlag = 0;
6971 char promoChoice = NULLCHAR;
6973 static TimeMark lastClickTime, prevClickTime;
6975 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6977 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6979 if (clickType == Press) ErrorPopDown();
6981 x = EventToSquare(xPix, BOARD_WIDTH);
6982 y = EventToSquare(yPix, BOARD_HEIGHT);
6983 if (!flipView && y >= 0) {
6984 y = BOARD_HEIGHT - 1 - y;
6986 if (flipView && x >= 0) {
6987 x = BOARD_WIDTH - 1 - x;
6990 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6991 defaultPromoChoice = promoSweep;
6992 promoSweep = EmptySquare; // terminate sweep
6993 promoDefaultAltered = TRUE;
6994 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6997 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6998 if(clickType == Release) return; // ignore upclick of click-click destination
6999 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7000 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7001 if(gameInfo.holdingsWidth &&
7002 (WhiteOnMove(currentMove)
7003 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7004 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7005 // click in right holdings, for determining promotion piece
7006 ChessSquare p = boards[currentMove][y][x];
7007 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7008 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7009 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7010 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7015 DrawPosition(FALSE, boards[currentMove]);
7019 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7020 if(clickType == Press
7021 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7022 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7023 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7026 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7027 // could be static click on premove from-square: abort premove
7029 ClearPremoveHighlights();
7032 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7033 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7035 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7036 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7037 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7038 defaultPromoChoice = DefaultPromoChoice(side);
7041 autoQueen = appData.alwaysPromoteToQueen;
7045 gatingPiece = EmptySquare;
7046 if (clickType != Press) {
7047 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7048 DragPieceEnd(xPix, yPix); dragging = 0;
7049 DrawPosition(FALSE, NULL);
7053 doubleClick = FALSE;
7054 fromX = x; fromY = y; toX = toY = -1;
7055 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7056 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7057 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7059 if (OKToStartUserMove(fromX, fromY)) {
7061 MarkTargetSquares(0);
7062 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7063 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7064 promoSweep = defaultPromoChoice;
7065 selectFlag = 0; lastX = xPix; lastY = yPix;
7066 Sweep(0); // Pawn that is going to promote: preview promotion piece
7067 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7069 if (appData.highlightDragging) {
7070 SetHighlights(fromX, fromY, -1, -1);
7072 } else fromX = fromY = -1;
7078 if (clickType == Press && gameMode != EditPosition) {
7083 // ignore off-board to clicks
7084 if(y < 0 || x < 0) return;
7086 /* Check if clicking again on the same color piece */
7087 fromP = boards[currentMove][fromY][fromX];
7088 toP = boards[currentMove][y][x];
7089 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7090 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7091 WhitePawn <= toP && toP <= WhiteKing &&
7092 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7093 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7094 (BlackPawn <= fromP && fromP <= BlackKing &&
7095 BlackPawn <= toP && toP <= BlackKing &&
7096 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7097 !(fromP == BlackKing && toP == BlackRook && frc))) {
7098 /* Clicked again on same color piece -- changed his mind */
7099 second = (x == fromX && y == fromY);
7100 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7101 second = FALSE; // first double-click rather than scond click
7102 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7104 promoDefaultAltered = FALSE;
7105 MarkTargetSquares(1);
7106 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7107 if (appData.highlightDragging) {
7108 SetHighlights(x, y, -1, -1);
7112 if (OKToStartUserMove(x, y)) {
7113 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7114 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7115 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7116 gatingPiece = boards[currentMove][fromY][fromX];
7117 else gatingPiece = doubleClick ? fromP : EmptySquare;
7119 fromY = y; dragging = 1;
7120 MarkTargetSquares(0);
7121 DragPieceBegin(xPix, yPix, FALSE);
7122 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7123 promoSweep = defaultPromoChoice;
7124 selectFlag = 0; lastX = xPix; lastY = yPix;
7125 Sweep(0); // Pawn that is going to promote: preview promotion piece
7129 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7132 // ignore clicks on holdings
7133 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7136 if (clickType == Release && x == fromX && y == fromY) {
7137 DragPieceEnd(xPix, yPix); dragging = 0;
7139 // a deferred attempt to click-click move an empty square on top of a piece
7140 boards[currentMove][y][x] = EmptySquare;
7142 DrawPosition(FALSE, boards[currentMove]);
7143 fromX = fromY = -1; clearFlag = 0;
7146 if (appData.animateDragging) {
7147 /* Undo animation damage if any */
7148 DrawPosition(FALSE, NULL);
7151 /* Second up/down in same square; just abort move */
7154 gatingPiece = EmptySquare;
7157 ClearPremoveHighlights();
7159 /* First upclick in same square; start click-click mode */
7160 SetHighlights(x, y, -1, -1);
7167 /* we now have a different from- and (possibly off-board) to-square */
7168 /* Completed move */
7171 saveAnimate = appData.animate;
7172 MarkTargetSquares(1);
7173 if (clickType == Press) {
7174 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7175 // must be Edit Position mode with empty-square selected
7176 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7177 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7180 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7181 ChessSquare piece = boards[currentMove][fromY][fromX];
7182 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7183 promoSweep = defaultPromoChoice;
7184 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7185 selectFlag = 0; lastX = xPix; lastY = yPix;
7186 Sweep(0); // Pawn that is going to promote: preview promotion piece
7187 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7188 DrawPosition(FALSE, boards[currentMove]);
7191 /* Finish clickclick move */
7192 if (appData.animate || appData.highlightLastMove) {
7193 SetHighlights(fromX, fromY, toX, toY);
7198 /* Finish drag move */
7199 if (appData.highlightLastMove) {
7200 SetHighlights(fromX, fromY, toX, toY);
7204 DragPieceEnd(xPix, yPix); dragging = 0;
7205 /* Don't animate move and drag both */
7206 appData.animate = FALSE;
7209 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7210 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7211 ChessSquare piece = boards[currentMove][fromY][fromX];
7212 if(gameMode == EditPosition && piece != EmptySquare &&
7213 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7216 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7217 n = PieceToNumber(piece - (int)BlackPawn);
7218 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7219 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7220 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7222 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7223 n = PieceToNumber(piece);
7224 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7225 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7226 boards[currentMove][n][BOARD_WIDTH-2]++;
7228 boards[currentMove][fromY][fromX] = EmptySquare;
7232 DrawPosition(TRUE, boards[currentMove]);
7236 // off-board moves should not be highlighted
7237 if(x < 0 || y < 0) ClearHighlights();
7239 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7241 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7242 SetHighlights(fromX, fromY, toX, toY);
7243 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7244 // [HGM] super: promotion to captured piece selected from holdings
7245 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7246 promotionChoice = TRUE;
7247 // kludge follows to temporarily execute move on display, without promoting yet
7248 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7249 boards[currentMove][toY][toX] = p;
7250 DrawPosition(FALSE, boards[currentMove]);
7251 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7252 boards[currentMove][toY][toX] = q;
7253 DisplayMessage("Click in holdings to choose piece", "");
7258 int oldMove = currentMove;
7259 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7260 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7261 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7262 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7263 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7264 DrawPosition(TRUE, boards[currentMove]);
7267 appData.animate = saveAnimate;
7268 if (appData.animate || appData.animateDragging) {
7269 /* Undo animation damage if needed */
7270 DrawPosition(FALSE, NULL);
7275 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7276 { // front-end-free part taken out of PieceMenuPopup
7277 int whichMenu; int xSqr, ySqr;
7279 if(seekGraphUp) { // [HGM] seekgraph
7280 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7281 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7285 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7286 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7287 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7288 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7289 if(action == Press) {
7290 originalFlip = flipView;
7291 flipView = !flipView; // temporarily flip board to see game from partners perspective
7292 DrawPosition(TRUE, partnerBoard);
7293 DisplayMessage(partnerStatus, "");
7295 } else if(action == Release) {
7296 flipView = originalFlip;
7297 DrawPosition(TRUE, boards[currentMove]);
7303 xSqr = EventToSquare(x, BOARD_WIDTH);
7304 ySqr = EventToSquare(y, BOARD_HEIGHT);
7305 if (action == Release) {
7306 if(pieceSweep != EmptySquare) {
7307 EditPositionMenuEvent(pieceSweep, toX, toY);
7308 pieceSweep = EmptySquare;
7309 } else UnLoadPV(); // [HGM] pv
7311 if (action != Press) return -2; // return code to be ignored
7314 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7316 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7317 if (xSqr < 0 || ySqr < 0) return -1;
7318 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7319 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7320 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7321 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7325 if(!appData.icsEngineAnalyze) return -1;
7326 case IcsPlayingWhite:
7327 case IcsPlayingBlack:
7328 if(!appData.zippyPlay) goto noZip;
7331 case MachinePlaysWhite:
7332 case MachinePlaysBlack:
7333 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7334 if (!appData.dropMenu) {
7336 return 2; // flag front-end to grab mouse events
7338 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7339 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7342 if (xSqr < 0 || ySqr < 0) return -1;
7343 if (!appData.dropMenu || appData.testLegality &&
7344 gameInfo.variant != VariantBughouse &&
7345 gameInfo.variant != VariantCrazyhouse) return -1;
7346 whichMenu = 1; // drop menu
7352 if (((*fromX = xSqr) < 0) ||
7353 ((*fromY = ySqr) < 0)) {
7354 *fromX = *fromY = -1;
7358 *fromX = BOARD_WIDTH - 1 - *fromX;
7360 *fromY = BOARD_HEIGHT - 1 - *fromY;
7366 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7368 // char * hint = lastHint;
7369 FrontEndProgramStats stats;
7371 stats.which = cps == &first ? 0 : 1;
7372 stats.depth = cpstats->depth;
7373 stats.nodes = cpstats->nodes;
7374 stats.score = cpstats->score;
7375 stats.time = cpstats->time;
7376 stats.pv = cpstats->movelist;
7377 stats.hint = lastHint;
7378 stats.an_move_index = 0;
7379 stats.an_move_count = 0;
7381 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7382 stats.hint = cpstats->move_name;
7383 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7384 stats.an_move_count = cpstats->nr_moves;
7387 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
7389 SetProgramStats( &stats );
7393 ClearEngineOutputPane (int which)
7395 static FrontEndProgramStats dummyStats;
7396 dummyStats.which = which;
7397 dummyStats.pv = "#";
7398 SetProgramStats( &dummyStats );
7401 #define MAXPLAYERS 500
7404 TourneyStandings (int display)
7406 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7407 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7408 char result, *p, *names[MAXPLAYERS];
7410 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7411 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7412 names[0] = p = strdup(appData.participants);
7413 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7415 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7417 while(result = appData.results[nr]) {
7418 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7419 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7420 wScore = bScore = 0;
7422 case '+': wScore = 2; break;
7423 case '-': bScore = 2; break;
7424 case '=': wScore = bScore = 1; break;
7426 case '*': return strdup("busy"); // tourney not finished
7434 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7435 for(w=0; w<nPlayers; w++) {
7437 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7438 ranking[w] = b; points[w] = bScore; score[b] = -2;
7440 p = malloc(nPlayers*34+1);
7441 for(w=0; w<nPlayers && w<display; w++)
7442 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7448 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7449 { // count all piece types
7451 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7452 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7453 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7456 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7457 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7458 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7459 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7460 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7461 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7466 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7468 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7469 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7471 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7472 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7473 if(myPawns == 2 && nMine == 3) // KPP
7474 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7475 if(myPawns == 1 && nMine == 2) // KP
7476 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7477 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7478 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7479 if(myPawns) return FALSE;
7480 if(pCnt[WhiteRook+side])
7481 return pCnt[BlackRook-side] ||
7482 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7483 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7484 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7485 if(pCnt[WhiteCannon+side]) {
7486 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7487 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7489 if(pCnt[WhiteKnight+side])
7490 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7495 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7497 VariantClass v = gameInfo.variant;
7499 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7500 if(v == VariantShatranj) return TRUE; // always winnable through baring
7501 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7502 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7504 if(v == VariantXiangqi) {
7505 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7507 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7508 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7509 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7510 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7511 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7512 if(stale) // we have at least one last-rank P plus perhaps C
7513 return majors // KPKX
7514 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7516 return pCnt[WhiteFerz+side] // KCAK
7517 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7518 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7519 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7521 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7522 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7524 if(nMine == 1) return FALSE; // bare King
7525 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
7526 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7527 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7528 // by now we have King + 1 piece (or multiple Bishops on the same color)
7529 if(pCnt[WhiteKnight+side])
7530 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7531 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7532 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7534 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7535 if(pCnt[WhiteAlfil+side])
7536 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7537 if(pCnt[WhiteWazir+side])
7538 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7545 CompareWithRights (Board b1, Board b2)
7548 if(!CompareBoards(b1, b2)) return FALSE;
7549 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7550 /* compare castling rights */
7551 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7552 rights++; /* King lost rights, while rook still had them */
7553 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7554 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7555 rights++; /* but at least one rook lost them */
7557 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7559 if( b1[CASTLING][5] != NoRights ) {
7560 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7567 Adjudicate (ChessProgramState *cps)
7568 { // [HGM] some adjudications useful with buggy engines
7569 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7570 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7571 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7572 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7573 int k, count = 0; static int bare = 1;
7574 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7575 Boolean canAdjudicate = !appData.icsActive;
7577 // most tests only when we understand the game, i.e. legality-checking on
7578 if( appData.testLegality )
7579 { /* [HGM] Some more adjudications for obstinate engines */
7580 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7581 static int moveCount = 6;
7583 char *reason = NULL;
7585 /* Count what is on board. */
7586 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7588 /* Some material-based adjudications that have to be made before stalemate test */
7589 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7590 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7591 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7592 if(canAdjudicate && appData.checkMates) {
7594 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7595 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7596 "Xboard adjudication: King destroyed", GE_XBOARD );
7601 /* Bare King in Shatranj (loses) or Losers (wins) */
7602 if( nrW == 1 || nrB == 1) {
7603 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7604 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7605 if(canAdjudicate && appData.checkMates) {
7607 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7608 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7609 "Xboard adjudication: Bare king", GE_XBOARD );
7613 if( gameInfo.variant == VariantShatranj && --bare < 0)
7615 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7616 if(canAdjudicate && appData.checkMates) {
7617 /* but only adjudicate if adjudication enabled */
7619 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7620 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7621 "Xboard adjudication: Bare king", GE_XBOARD );
7628 // don't wait for engine to announce game end if we can judge ourselves
7629 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7631 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7632 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7633 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7634 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7637 reason = "Xboard adjudication: 3rd check";
7638 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7648 reason = "Xboard adjudication: Stalemate";
7649 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7650 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7651 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7652 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7653 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7654 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7655 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7656 EP_CHECKMATE : EP_WINS);
7657 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7658 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7662 reason = "Xboard adjudication: Checkmate";
7663 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7667 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7669 result = GameIsDrawn; break;
7671 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7673 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7677 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7679 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680 GameEnds( result, reason, GE_XBOARD );
7684 /* Next absolutely insufficient mating material. */
7685 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7686 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7687 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7689 /* always flag draws, for judging claims */
7690 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7692 if(canAdjudicate && appData.materialDraws) {
7693 /* but only adjudicate them if adjudication enabled */
7694 if(engineOpponent) {
7695 SendToProgram("force\n", engineOpponent); // suppress reply
7696 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7698 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7703 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7704 if(gameInfo.variant == VariantXiangqi ?
7705 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7707 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7708 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7709 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7710 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7712 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7713 { /* if the first 3 moves do not show a tactical win, declare draw */
7714 if(engineOpponent) {
7715 SendToProgram("force\n", engineOpponent); // suppress reply
7716 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7721 } else moveCount = 6;
7724 // Repetition draws and 50-move rule can be applied independently of legality testing
7726 /* Check for rep-draws */
7728 for(k = forwardMostMove-2;
7729 k>=backwardMostMove && k>=forwardMostMove-100 &&
7730 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7731 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7734 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7735 /* compare castling rights */
7736 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7737 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7738 rights++; /* King lost rights, while rook still had them */
7739 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7740 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7741 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7742 rights++; /* but at least one rook lost them */
7744 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7745 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7747 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7748 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7749 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7752 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7753 && appData.drawRepeats > 1) {
7754 /* adjudicate after user-specified nr of repeats */
7755 int result = GameIsDrawn;
7756 char *details = "XBoard adjudication: repetition draw";
7757 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7758 // [HGM] xiangqi: check for forbidden perpetuals
7759 int m, ourPerpetual = 1, hisPerpetual = 1;
7760 for(m=forwardMostMove; m>k; m-=2) {
7761 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7762 ourPerpetual = 0; // the current mover did not always check
7763 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7764 hisPerpetual = 0; // the opponent did not always check
7766 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7767 ourPerpetual, hisPerpetual);
7768 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7769 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7770 details = "Xboard adjudication: perpetual checking";
7772 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7773 break; // (or we would have caught him before). Abort repetition-checking loop.
7775 // Now check for perpetual chases
7776 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7777 hisPerpetual = PerpetualChase(k, forwardMostMove);
7778 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7779 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7780 static char resdet[MSG_SIZ];
7781 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7783 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7785 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7786 break; // Abort repetition-checking loop.
7788 // if neither of us is checking or chasing all the time, or both are, it is draw
7790 if(engineOpponent) {
7791 SendToProgram("force\n", engineOpponent); // suppress reply
7792 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7794 GameEnds( result, details, GE_XBOARD );
7797 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7798 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7802 /* Now we test for 50-move draws. Determine ply count */
7803 count = forwardMostMove;
7804 /* look for last irreversble move */
7805 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7807 /* if we hit starting position, add initial plies */
7808 if( count == backwardMostMove )
7809 count -= initialRulePlies;
7810 count = forwardMostMove - count;
7811 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7812 // adjust reversible move counter for checks in Xiangqi
7813 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7814 if(i < backwardMostMove) i = backwardMostMove;
7815 while(i <= forwardMostMove) {
7816 lastCheck = inCheck; // check evasion does not count
7817 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7818 if(inCheck || lastCheck) count--; // check does not count
7823 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7824 /* this is used to judge if draw claims are legal */
7825 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7826 if(engineOpponent) {
7827 SendToProgram("force\n", engineOpponent); // suppress reply
7828 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7830 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7834 /* if draw offer is pending, treat it as a draw claim
7835 * when draw condition present, to allow engines a way to
7836 * claim draws before making their move to avoid a race
7837 * condition occurring after their move
7839 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7841 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7842 p = "Draw claim: 50-move rule";
7843 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7844 p = "Draw claim: 3-fold repetition";
7845 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7846 p = "Draw claim: insufficient mating material";
7847 if( p != NULL && canAdjudicate) {
7848 if(engineOpponent) {
7849 SendToProgram("force\n", engineOpponent); // suppress reply
7850 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7852 GameEnds( GameIsDrawn, p, GE_XBOARD );
7857 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7858 if(engineOpponent) {
7859 SendToProgram("force\n", engineOpponent); // suppress reply
7860 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7862 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7869 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7870 { // [HGM] book: this routine intercepts moves to simulate book replies
7871 char *bookHit = NULL;
7873 //first determine if the incoming move brings opponent into his book
7874 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7875 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7876 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7877 if(bookHit != NULL && !cps->bookSuspend) {
7878 // make sure opponent is not going to reply after receiving move to book position
7879 SendToProgram("force\n", cps);
7880 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7882 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7883 // now arrange restart after book miss
7885 // after a book hit we never send 'go', and the code after the call to this routine
7886 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7887 char buf[MSG_SIZ], *move = bookHit;
7889 int fromX, fromY, toX, toY;
7893 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7894 &fromX, &fromY, &toX, &toY, &promoChar)) {
7895 (void) CoordsToAlgebraic(boards[forwardMostMove],
7896 PosFlags(forwardMostMove),
7897 fromY, fromX, toY, toX, promoChar, move);
7899 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7903 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7904 SendToProgram(buf, cps);
7905 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7906 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7907 SendToProgram("go\n", cps);
7908 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7909 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7910 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7911 SendToProgram("go\n", cps);
7912 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7914 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7918 ChessProgramState *savedState;
7920 DeferredBookMove (void)
7922 if(savedState->lastPing != savedState->lastPong)
7923 ScheduleDelayedEvent(DeferredBookMove, 10);
7925 HandleMachineMove(savedMessage, savedState);
7928 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7931 HandleMachineMove (char *message, ChessProgramState *cps)
7933 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7934 char realname[MSG_SIZ];
7935 int fromX, fromY, toX, toY;
7942 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7943 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7944 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7945 DisplayError(_("Invalid pairing from pairing engine"), 0);
7948 pairingReceived = 1;
7950 return; // Skim the pairing messages here.
7955 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7957 * Kludge to ignore BEL characters
7959 while (*message == '\007') message++;
7962 * [HGM] engine debug message: ignore lines starting with '#' character
7964 if(cps->debug && *message == '#') return;
7967 * Look for book output
7969 if (cps == &first && bookRequested) {
7970 if (message[0] == '\t' || message[0] == ' ') {
7971 /* Part of the book output is here; append it */
7972 strcat(bookOutput, message);
7973 strcat(bookOutput, " \n");
7975 } else if (bookOutput[0] != NULLCHAR) {
7976 /* All of book output has arrived; display it */
7977 char *p = bookOutput;
7978 while (*p != NULLCHAR) {
7979 if (*p == '\t') *p = ' ';
7982 DisplayInformation(bookOutput);
7983 bookRequested = FALSE;
7984 /* Fall through to parse the current output */
7989 * Look for machine move.
7991 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7992 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7994 /* This method is only useful on engines that support ping */
7995 if (cps->lastPing != cps->lastPong) {
7996 if (gameMode == BeginningOfGame) {
7997 /* Extra move from before last new; ignore */
7998 if (appData.debugMode) {
7999 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8002 if (appData.debugMode) {
8003 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8004 cps->which, gameMode);
8007 SendToProgram("undo\n", cps);
8013 case BeginningOfGame:
8014 /* Extra move from before last reset; ignore */
8015 if (appData.debugMode) {
8016 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8023 /* Extra move after we tried to stop. The mode test is
8024 not a reliable way of detecting this problem, but it's
8025 the best we can do on engines that don't support ping.
8027 if (appData.debugMode) {
8028 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8029 cps->which, gameMode);
8031 SendToProgram("undo\n", cps);
8034 case MachinePlaysWhite:
8035 case IcsPlayingWhite:
8036 machineWhite = TRUE;
8039 case MachinePlaysBlack:
8040 case IcsPlayingBlack:
8041 machineWhite = FALSE;
8044 case TwoMachinesPlay:
8045 machineWhite = (cps->twoMachinesColor[0] == 'w');
8048 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8049 if (appData.debugMode) {
8051 "Ignoring move out of turn by %s, gameMode %d"
8052 ", forwardMost %d\n",
8053 cps->which, gameMode, forwardMostMove);
8058 if(cps->alphaRank) AlphaRank(machineMove, 4);
8059 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8060 &fromX, &fromY, &toX, &toY, &promoChar)) {
8061 /* Machine move could not be parsed; ignore it. */
8062 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8063 machineMove, _(cps->which));
8064 DisplayError(buf1, 0);
8065 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8066 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8067 if (gameMode == TwoMachinesPlay) {
8068 GameEnds(machineWhite ? BlackWins : WhiteWins,
8074 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8075 /* So we have to redo legality test with true e.p. status here, */
8076 /* to make sure an illegal e.p. capture does not slip through, */
8077 /* to cause a forfeit on a justified illegal-move complaint */
8078 /* of the opponent. */
8079 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8081 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8082 fromY, fromX, toY, toX, promoChar);
8083 if(moveType == IllegalMove) {
8084 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8085 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8086 GameEnds(machineWhite ? BlackWins : WhiteWins,
8089 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8090 /* [HGM] Kludge to handle engines that send FRC-style castling
8091 when they shouldn't (like TSCP-Gothic) */
8093 case WhiteASideCastleFR:
8094 case BlackASideCastleFR:
8096 currentMoveString[2]++;
8098 case WhiteHSideCastleFR:
8099 case BlackHSideCastleFR:
8101 currentMoveString[2]--;
8103 default: ; // nothing to do, but suppresses warning of pedantic compilers
8106 hintRequested = FALSE;
8107 lastHint[0] = NULLCHAR;
8108 bookRequested = FALSE;
8109 /* Program may be pondering now */
8110 cps->maybeThinking = TRUE;
8111 if (cps->sendTime == 2) cps->sendTime = 1;
8112 if (cps->offeredDraw) cps->offeredDraw--;
8114 /* [AS] Save move info*/
8115 pvInfoList[ forwardMostMove ].score = programStats.score;
8116 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8117 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8119 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8121 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8122 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8125 while( count < adjudicateLossPlies ) {
8126 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8129 score = -score; /* Flip score for winning side */
8132 if( score > adjudicateLossThreshold ) {
8139 if( count >= adjudicateLossPlies ) {
8140 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8142 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8143 "Xboard adjudication",
8150 if(Adjudicate(cps)) {
8151 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8152 return; // [HGM] adjudicate: for all automatic game ends
8156 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8158 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8159 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8161 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8163 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8165 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8166 char buf[3*MSG_SIZ];
8168 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8169 programStats.score / 100.,
8171 programStats.time / 100.,
8172 (unsigned int)programStats.nodes,
8173 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8174 programStats.movelist);
8176 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8181 /* [AS] Clear stats for next move */
8182 ClearProgramStats();
8183 thinkOutput[0] = NULLCHAR;
8184 hiddenThinkOutputState = 0;
8187 if (gameMode == TwoMachinesPlay) {
8188 /* [HGM] relaying draw offers moved to after reception of move */
8189 /* and interpreting offer as claim if it brings draw condition */
8190 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8191 SendToProgram("draw\n", cps->other);
8193 if (cps->other->sendTime) {
8194 SendTimeRemaining(cps->other,
8195 cps->other->twoMachinesColor[0] == 'w');
8197 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8198 if (firstMove && !bookHit) {
8200 if (cps->other->useColors) {
8201 SendToProgram(cps->other->twoMachinesColor, cps->other);
8203 SendToProgram("go\n", cps->other);
8205 cps->other->maybeThinking = TRUE;
8208 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8210 if (!pausing && appData.ringBellAfterMoves) {
8215 * Reenable menu items that were disabled while
8216 * machine was thinking
8218 if (gameMode != TwoMachinesPlay)
8219 SetUserThinkingEnables();
8221 // [HGM] book: after book hit opponent has received move and is now in force mode
8222 // force the book reply into it, and then fake that it outputted this move by jumping
8223 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8225 static char bookMove[MSG_SIZ]; // a bit generous?
8227 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8228 strcat(bookMove, bookHit);
8231 programStats.nodes = programStats.depth = programStats.time =
8232 programStats.score = programStats.got_only_move = 0;
8233 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8235 if(cps->lastPing != cps->lastPong) {
8236 savedMessage = message; // args for deferred call
8238 ScheduleDelayedEvent(DeferredBookMove, 10);
8247 /* Set special modes for chess engines. Later something general
8248 * could be added here; for now there is just one kludge feature,
8249 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8250 * when "xboard" is given as an interactive command.
8252 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8253 cps->useSigint = FALSE;
8254 cps->useSigterm = FALSE;
8256 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8257 ParseFeatures(message+8, cps);
8258 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8261 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8262 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8263 int dummy, s=6; char buf[MSG_SIZ];
8264 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8265 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8266 if(startedFromSetupPosition) return;
8267 ParseFEN(boards[0], &dummy, message+s);
8268 DrawPosition(TRUE, boards[0]);
8269 startedFromSetupPosition = TRUE;
8272 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8273 * want this, I was asked to put it in, and obliged.
8275 if (!strncmp(message, "setboard ", 9)) {
8276 Board initial_position;
8278 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8280 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8281 DisplayError(_("Bad FEN received from engine"), 0);
8285 CopyBoard(boards[0], initial_position);
8286 initialRulePlies = FENrulePlies;
8287 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8288 else gameMode = MachinePlaysBlack;
8289 DrawPosition(FALSE, boards[currentMove]);
8295 * Look for communication commands
8297 if (!strncmp(message, "telluser ", 9)) {
8298 if(message[9] == '\\' && message[10] == '\\')
8299 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8301 DisplayNote(message + 9);
8304 if (!strncmp(message, "tellusererror ", 14)) {
8306 if(message[14] == '\\' && message[15] == '\\')
8307 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8309 DisplayError(message + 14, 0);
8312 if (!strncmp(message, "tellopponent ", 13)) {
8313 if (appData.icsActive) {
8315 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8319 DisplayNote(message + 13);
8323 if (!strncmp(message, "tellothers ", 11)) {
8324 if (appData.icsActive) {
8326 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8332 if (!strncmp(message, "tellall ", 8)) {
8333 if (appData.icsActive) {
8335 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8339 DisplayNote(message + 8);
8343 if (strncmp(message, "warning", 7) == 0) {
8344 /* Undocumented feature, use tellusererror in new code */
8345 DisplayError(message, 0);
8348 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8349 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8350 strcat(realname, " query");
8351 AskQuestion(realname, buf2, buf1, cps->pr);
8354 /* Commands from the engine directly to ICS. We don't allow these to be
8355 * sent until we are logged on. Crafty kibitzes have been known to
8356 * interfere with the login process.
8359 if (!strncmp(message, "tellics ", 8)) {
8360 SendToICS(message + 8);
8364 if (!strncmp(message, "tellicsnoalias ", 15)) {
8365 SendToICS(ics_prefix);
8366 SendToICS(message + 15);
8370 /* The following are for backward compatibility only */
8371 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8372 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8373 SendToICS(ics_prefix);
8379 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8383 * If the move is illegal, cancel it and redraw the board.
8384 * Also deal with other error cases. Matching is rather loose
8385 * here to accommodate engines written before the spec.
8387 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8388 strncmp(message, "Error", 5) == 0) {
8389 if (StrStr(message, "name") ||
8390 StrStr(message, "rating") || StrStr(message, "?") ||
8391 StrStr(message, "result") || StrStr(message, "board") ||
8392 StrStr(message, "bk") || StrStr(message, "computer") ||
8393 StrStr(message, "variant") || StrStr(message, "hint") ||
8394 StrStr(message, "random") || StrStr(message, "depth") ||
8395 StrStr(message, "accepted")) {
8398 if (StrStr(message, "protover")) {
8399 /* Program is responding to input, so it's apparently done
8400 initializing, and this error message indicates it is
8401 protocol version 1. So we don't need to wait any longer
8402 for it to initialize and send feature commands. */
8403 FeatureDone(cps, 1);
8404 cps->protocolVersion = 1;
8407 cps->maybeThinking = FALSE;
8409 if (StrStr(message, "draw")) {
8410 /* Program doesn't have "draw" command */
8411 cps->sendDrawOffers = 0;
8414 if (cps->sendTime != 1 &&
8415 (StrStr(message, "time") || StrStr(message, "otim"))) {
8416 /* Program apparently doesn't have "time" or "otim" command */
8420 if (StrStr(message, "analyze")) {
8421 cps->analysisSupport = FALSE;
8422 cps->analyzing = FALSE;
8423 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8424 EditGameEvent(); // [HGM] try to preserve loaded game
8425 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8426 DisplayError(buf2, 0);
8429 if (StrStr(message, "(no matching move)st")) {
8430 /* Special kludge for GNU Chess 4 only */
8431 cps->stKludge = TRUE;
8432 SendTimeControl(cps, movesPerSession, timeControl,
8433 timeIncrement, appData.searchDepth,
8437 if (StrStr(message, "(no matching move)sd")) {
8438 /* Special kludge for GNU Chess 4 only */
8439 cps->sdKludge = TRUE;
8440 SendTimeControl(cps, movesPerSession, timeControl,
8441 timeIncrement, appData.searchDepth,
8445 if (!StrStr(message, "llegal")) {
8448 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8449 gameMode == IcsIdle) return;
8450 if (forwardMostMove <= backwardMostMove) return;
8451 if (pausing) PauseEvent();
8452 if(appData.forceIllegal) {
8453 // [HGM] illegal: machine refused move; force position after move into it
8454 SendToProgram("force\n", cps);
8455 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8456 // we have a real problem now, as SendBoard will use the a2a3 kludge
8457 // when black is to move, while there might be nothing on a2 or black
8458 // might already have the move. So send the board as if white has the move.
8459 // But first we must change the stm of the engine, as it refused the last move
8460 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8461 if(WhiteOnMove(forwardMostMove)) {
8462 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8463 SendBoard(cps, forwardMostMove); // kludgeless board
8465 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8466 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8467 SendBoard(cps, forwardMostMove+1); // kludgeless board
8469 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8470 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8471 gameMode == TwoMachinesPlay)
8472 SendToProgram("go\n", cps);
8475 if (gameMode == PlayFromGameFile) {
8476 /* Stop reading this game file */
8477 gameMode = EditGame;
8480 /* [HGM] illegal-move claim should forfeit game when Xboard */
8481 /* only passes fully legal moves */
8482 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8483 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8484 "False illegal-move claim", GE_XBOARD );
8485 return; // do not take back move we tested as valid
8487 currentMove = forwardMostMove-1;
8488 DisplayMove(currentMove-1); /* before DisplayMoveError */
8489 SwitchClocks(forwardMostMove-1); // [HGM] race
8490 DisplayBothClocks();
8491 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8492 parseList[currentMove], _(cps->which));
8493 DisplayMoveError(buf1);
8494 DrawPosition(FALSE, boards[currentMove]);
8496 SetUserThinkingEnables();
8499 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8500 /* Program has a broken "time" command that
8501 outputs a string not ending in newline.
8507 * If chess program startup fails, exit with an error message.
8508 * Attempts to recover here are futile. [HGM] Well, we try anyway
8510 if ((StrStr(message, "unknown host") != NULL)
8511 || (StrStr(message, "No remote directory") != NULL)
8512 || (StrStr(message, "not found") != NULL)
8513 || (StrStr(message, "No such file") != NULL)
8514 || (StrStr(message, "can't alloc") != NULL)
8515 || (StrStr(message, "Permission denied") != NULL)) {
8517 cps->maybeThinking = FALSE;
8518 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8519 _(cps->which), cps->program, cps->host, message);
8520 RemoveInputSource(cps->isr);
8521 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8523 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8526 appData.noChessProgram = TRUE;
8527 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8528 gameMode = BeginningOfGame; ModeHighlight();
8531 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8532 DisplayMessage("", ""); // erase waiting message
8533 DisplayError(buf1, 0);
8539 * Look for hint output
8541 if (sscanf(message, "Hint: %s", buf1) == 1) {
8542 if (cps == &first && hintRequested) {
8543 hintRequested = FALSE;
8544 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8545 &fromX, &fromY, &toX, &toY, &promoChar)) {
8546 (void) CoordsToAlgebraic(boards[forwardMostMove],
8547 PosFlags(forwardMostMove),
8548 fromY, fromX, toY, toX, promoChar, buf1);
8549 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8550 DisplayInformation(buf2);
8552 /* Hint move could not be parsed!? */
8553 snprintf(buf2, sizeof(buf2),
8554 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8555 buf1, _(cps->which));
8556 DisplayError(buf2, 0);
8559 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8565 * Ignore other messages if game is not in progress
8567 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8568 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8571 * look for win, lose, draw, or draw offer
8573 if (strncmp(message, "1-0", 3) == 0) {
8574 char *p, *q, *r = "";
8575 p = strchr(message, '{');
8583 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8585 } else if (strncmp(message, "0-1", 3) == 0) {
8586 char *p, *q, *r = "";
8587 p = strchr(message, '{');
8595 /* Kludge for Arasan 4.1 bug */
8596 if (strcmp(r, "Black resigns") == 0) {
8597 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8600 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8602 } else if (strncmp(message, "1/2", 3) == 0) {
8603 char *p, *q, *r = "";
8604 p = strchr(message, '{');
8613 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8616 } else if (strncmp(message, "White resign", 12) == 0) {
8617 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8619 } else if (strncmp(message, "Black resign", 12) == 0) {
8620 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8622 } else if (strncmp(message, "White matches", 13) == 0 ||
8623 strncmp(message, "Black matches", 13) == 0 ) {
8624 /* [HGM] ignore GNUShogi noises */
8626 } else if (strncmp(message, "White", 5) == 0 &&
8627 message[5] != '(' &&
8628 StrStr(message, "Black") == NULL) {
8629 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8631 } else if (strncmp(message, "Black", 5) == 0 &&
8632 message[5] != '(') {
8633 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8635 } else if (strcmp(message, "resign") == 0 ||
8636 strcmp(message, "computer resigns") == 0) {
8638 case MachinePlaysBlack:
8639 case IcsPlayingBlack:
8640 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8642 case MachinePlaysWhite:
8643 case IcsPlayingWhite:
8644 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8646 case TwoMachinesPlay:
8647 if (cps->twoMachinesColor[0] == 'w')
8648 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8650 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8657 } else if (strncmp(message, "opponent mates", 14) == 0) {
8659 case MachinePlaysBlack:
8660 case IcsPlayingBlack:
8661 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8663 case MachinePlaysWhite:
8664 case IcsPlayingWhite:
8665 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8667 case TwoMachinesPlay:
8668 if (cps->twoMachinesColor[0] == 'w')
8669 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8671 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8678 } else if (strncmp(message, "computer mates", 14) == 0) {
8680 case MachinePlaysBlack:
8681 case IcsPlayingBlack:
8682 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8684 case MachinePlaysWhite:
8685 case IcsPlayingWhite:
8686 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8688 case TwoMachinesPlay:
8689 if (cps->twoMachinesColor[0] == 'w')
8690 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8692 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8699 } else if (strncmp(message, "checkmate", 9) == 0) {
8700 if (WhiteOnMove(forwardMostMove)) {
8701 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8703 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8706 } else if (strstr(message, "Draw") != NULL ||
8707 strstr(message, "game is a draw") != NULL) {
8708 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8710 } else if (strstr(message, "offer") != NULL &&
8711 strstr(message, "draw") != NULL) {
8713 if (appData.zippyPlay && first.initDone) {
8714 /* Relay offer to ICS */
8715 SendToICS(ics_prefix);
8716 SendToICS("draw\n");
8719 cps->offeredDraw = 2; /* valid until this engine moves twice */
8720 if (gameMode == TwoMachinesPlay) {
8721 if (cps->other->offeredDraw) {
8722 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8723 /* [HGM] in two-machine mode we delay relaying draw offer */
8724 /* until after we also have move, to see if it is really claim */
8726 } else if (gameMode == MachinePlaysWhite ||
8727 gameMode == MachinePlaysBlack) {
8728 if (userOfferedDraw) {
8729 DisplayInformation(_("Machine accepts your draw offer"));
8730 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8732 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8739 * Look for thinking output
8741 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8742 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8744 int plylev, mvleft, mvtot, curscore, time;
8745 char mvname[MOVE_LEN];
8749 int prefixHint = FALSE;
8750 mvname[0] = NULLCHAR;
8753 case MachinePlaysBlack:
8754 case IcsPlayingBlack:
8755 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8757 case MachinePlaysWhite:
8758 case IcsPlayingWhite:
8759 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8764 case IcsObserving: /* [DM] icsEngineAnalyze */
8765 if (!appData.icsEngineAnalyze) ignore = TRUE;
8767 case TwoMachinesPlay:
8768 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8778 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8780 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8781 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8783 if (plyext != ' ' && plyext != '\t') {
8787 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8788 if( cps->scoreIsAbsolute &&
8789 ( gameMode == MachinePlaysBlack ||
8790 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8791 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8792 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8793 !WhiteOnMove(currentMove)
8796 curscore = -curscore;
8799 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8801 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8804 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8805 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8806 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8807 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8808 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8809 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8811 } else DisplayError(_("failed writing PV"), 0);
8814 tempStats.depth = plylev;
8815 tempStats.nodes = nodes;
8816 tempStats.time = time;
8817 tempStats.score = curscore;
8818 tempStats.got_only_move = 0;
8820 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8823 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8824 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8825 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8826 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8827 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8828 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8829 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8830 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8833 /* Buffer overflow protection */
8834 if (pv[0] != NULLCHAR) {
8835 if (strlen(pv) >= sizeof(tempStats.movelist)
8836 && appData.debugMode) {
8838 "PV is too long; using the first %u bytes.\n",
8839 (unsigned) sizeof(tempStats.movelist) - 1);
8842 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8844 sprintf(tempStats.movelist, " no PV\n");
8847 if (tempStats.seen_stat) {
8848 tempStats.ok_to_send = 1;
8851 if (strchr(tempStats.movelist, '(') != NULL) {
8852 tempStats.line_is_book = 1;
8853 tempStats.nr_moves = 0;
8854 tempStats.moves_left = 0;
8856 tempStats.line_is_book = 0;
8859 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8860 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8862 SendProgramStatsToFrontend( cps, &tempStats );
8865 [AS] Protect the thinkOutput buffer from overflow... this
8866 is only useful if buf1 hasn't overflowed first!
8868 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8870 (gameMode == TwoMachinesPlay ?
8871 ToUpper(cps->twoMachinesColor[0]) : ' '),
8872 ((double) curscore) / 100.0,
8873 prefixHint ? lastHint : "",
8874 prefixHint ? " " : "" );
8876 if( buf1[0] != NULLCHAR ) {
8877 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8879 if( strlen(pv) > max_len ) {
8880 if( appData.debugMode) {
8881 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8883 pv[max_len+1] = '\0';
8886 strcat( thinkOutput, pv);
8889 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8890 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8891 DisplayMove(currentMove - 1);
8895 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8896 /* crafty (9.25+) says "(only move) <move>"
8897 * if there is only 1 legal move
8899 sscanf(p, "(only move) %s", buf1);
8900 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8901 sprintf(programStats.movelist, "%s (only move)", buf1);
8902 programStats.depth = 1;
8903 programStats.nr_moves = 1;
8904 programStats.moves_left = 1;
8905 programStats.nodes = 1;
8906 programStats.time = 1;
8907 programStats.got_only_move = 1;
8909 /* Not really, but we also use this member to
8910 mean "line isn't going to change" (Crafty
8911 isn't searching, so stats won't change) */
8912 programStats.line_is_book = 1;
8914 SendProgramStatsToFrontend( cps, &programStats );
8916 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8917 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8918 DisplayMove(currentMove - 1);
8921 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8922 &time, &nodes, &plylev, &mvleft,
8923 &mvtot, mvname) >= 5) {
8924 /* The stat01: line is from Crafty (9.29+) in response
8925 to the "." command */
8926 programStats.seen_stat = 1;
8927 cps->maybeThinking = TRUE;
8929 if (programStats.got_only_move || !appData.periodicUpdates)
8932 programStats.depth = plylev;
8933 programStats.time = time;
8934 programStats.nodes = nodes;
8935 programStats.moves_left = mvleft;
8936 programStats.nr_moves = mvtot;
8937 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8938 programStats.ok_to_send = 1;
8939 programStats.movelist[0] = '\0';
8941 SendProgramStatsToFrontend( cps, &programStats );
8945 } else if (strncmp(message,"++",2) == 0) {
8946 /* Crafty 9.29+ outputs this */
8947 programStats.got_fail = 2;
8950 } else if (strncmp(message,"--",2) == 0) {
8951 /* Crafty 9.29+ outputs this */
8952 programStats.got_fail = 1;
8955 } else if (thinkOutput[0] != NULLCHAR &&
8956 strncmp(message, " ", 4) == 0) {
8957 unsigned message_len;
8960 while (*p && *p == ' ') p++;
8962 message_len = strlen( p );
8964 /* [AS] Avoid buffer overflow */
8965 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8966 strcat(thinkOutput, " ");
8967 strcat(thinkOutput, p);
8970 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8971 strcat(programStats.movelist, " ");
8972 strcat(programStats.movelist, p);
8975 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8976 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8977 DisplayMove(currentMove - 1);
8985 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8986 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8988 ChessProgramStats cpstats;
8990 if (plyext != ' ' && plyext != '\t') {
8994 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8995 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8996 curscore = -curscore;
8999 cpstats.depth = plylev;
9000 cpstats.nodes = nodes;
9001 cpstats.time = time;
9002 cpstats.score = curscore;
9003 cpstats.got_only_move = 0;
9004 cpstats.movelist[0] = '\0';
9006 if (buf1[0] != NULLCHAR) {
9007 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9010 cpstats.ok_to_send = 0;
9011 cpstats.line_is_book = 0;
9012 cpstats.nr_moves = 0;
9013 cpstats.moves_left = 0;
9015 SendProgramStatsToFrontend( cps, &cpstats );
9022 /* Parse a game score from the character string "game", and
9023 record it as the history of the current game. The game
9024 score is NOT assumed to start from the standard position.
9025 The display is not updated in any way.
9028 ParseGameHistory (char *game)
9031 int fromX, fromY, toX, toY, boardIndex;
9036 if (appData.debugMode)
9037 fprintf(debugFP, "Parsing game history: %s\n", game);
9039 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9040 gameInfo.site = StrSave(appData.icsHost);
9041 gameInfo.date = PGNDate();
9042 gameInfo.round = StrSave("-");
9044 /* Parse out names of players */
9045 while (*game == ' ') game++;
9047 while (*game != ' ') *p++ = *game++;
9049 gameInfo.white = StrSave(buf);
9050 while (*game == ' ') game++;
9052 while (*game != ' ' && *game != '\n') *p++ = *game++;
9054 gameInfo.black = StrSave(buf);
9057 boardIndex = blackPlaysFirst ? 1 : 0;
9060 yyboardindex = boardIndex;
9061 moveType = (ChessMove) Myylex();
9063 case IllegalMove: /* maybe suicide chess, etc. */
9064 if (appData.debugMode) {
9065 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9066 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9067 setbuf(debugFP, NULL);
9069 case WhitePromotion:
9070 case BlackPromotion:
9071 case WhiteNonPromotion:
9072 case BlackNonPromotion:
9074 case WhiteCapturesEnPassant:
9075 case BlackCapturesEnPassant:
9076 case WhiteKingSideCastle:
9077 case WhiteQueenSideCastle:
9078 case BlackKingSideCastle:
9079 case BlackQueenSideCastle:
9080 case WhiteKingSideCastleWild:
9081 case WhiteQueenSideCastleWild:
9082 case BlackKingSideCastleWild:
9083 case BlackQueenSideCastleWild:
9085 case WhiteHSideCastleFR:
9086 case WhiteASideCastleFR:
9087 case BlackHSideCastleFR:
9088 case BlackASideCastleFR:
9090 fromX = currentMoveString[0] - AAA;
9091 fromY = currentMoveString[1] - ONE;
9092 toX = currentMoveString[2] - AAA;
9093 toY = currentMoveString[3] - ONE;
9094 promoChar = currentMoveString[4];
9098 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9099 fromX = moveType == WhiteDrop ?
9100 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9101 (int) CharToPiece(ToLower(currentMoveString[0]));
9103 toX = currentMoveString[2] - AAA;
9104 toY = currentMoveString[3] - ONE;
9105 promoChar = NULLCHAR;
9109 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9110 if (appData.debugMode) {
9111 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9112 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9113 setbuf(debugFP, NULL);
9115 DisplayError(buf, 0);
9117 case ImpossibleMove:
9119 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9120 if (appData.debugMode) {
9121 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9122 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9123 setbuf(debugFP, NULL);
9125 DisplayError(buf, 0);
9128 if (boardIndex < backwardMostMove) {
9129 /* Oops, gap. How did that happen? */
9130 DisplayError(_("Gap in move list"), 0);
9133 backwardMostMove = blackPlaysFirst ? 1 : 0;
9134 if (boardIndex > forwardMostMove) {
9135 forwardMostMove = boardIndex;
9139 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9140 strcat(parseList[boardIndex-1], " ");
9141 strcat(parseList[boardIndex-1], yy_text);
9153 case GameUnfinished:
9154 if (gameMode == IcsExamining) {
9155 if (boardIndex < backwardMostMove) {
9156 /* Oops, gap. How did that happen? */
9159 backwardMostMove = blackPlaysFirst ? 1 : 0;
9162 gameInfo.result = moveType;
9163 p = strchr(yy_text, '{');
9164 if (p == NULL) p = strchr(yy_text, '(');
9167 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9169 q = strchr(p, *p == '{' ? '}' : ')');
9170 if (q != NULL) *q = NULLCHAR;
9173 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9174 gameInfo.resultDetails = StrSave(p);
9177 if (boardIndex >= forwardMostMove &&
9178 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9179 backwardMostMove = blackPlaysFirst ? 1 : 0;
9182 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9183 fromY, fromX, toY, toX, promoChar,
9184 parseList[boardIndex]);
9185 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9186 /* currentMoveString is set as a side-effect of yylex */
9187 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9188 strcat(moveList[boardIndex], "\n");
9190 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9191 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9197 if(gameInfo.variant != VariantShogi)
9198 strcat(parseList[boardIndex - 1], "+");
9202 strcat(parseList[boardIndex - 1], "#");
9209 /* Apply a move to the given board */
9211 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9213 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9214 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9216 /* [HGM] compute & store e.p. status and castling rights for new position */
9217 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9219 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9220 oldEP = (signed char)board[EP_STATUS];
9221 board[EP_STATUS] = EP_NONE;
9223 if (fromY == DROP_RANK) {
9225 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9226 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9229 piece = board[toY][toX] = (ChessSquare) fromX;
9233 if( board[toY][toX] != EmptySquare )
9234 board[EP_STATUS] = EP_CAPTURE;
9236 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9237 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9238 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9240 if( board[fromY][fromX] == WhitePawn ) {
9241 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9242 board[EP_STATUS] = EP_PAWN_MOVE;
9244 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9245 gameInfo.variant != VariantBerolina || toX < fromX)
9246 board[EP_STATUS] = toX | berolina;
9247 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9248 gameInfo.variant != VariantBerolina || toX > fromX)
9249 board[EP_STATUS] = toX;
9252 if( board[fromY][fromX] == BlackPawn ) {
9253 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9254 board[EP_STATUS] = EP_PAWN_MOVE;
9255 if( toY-fromY== -2) {
9256 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9257 gameInfo.variant != VariantBerolina || toX < fromX)
9258 board[EP_STATUS] = toX | berolina;
9259 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9260 gameInfo.variant != VariantBerolina || toX > fromX)
9261 board[EP_STATUS] = toX;
9265 for(i=0; i<nrCastlingRights; i++) {
9266 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9267 board[CASTLING][i] == toX && castlingRank[i] == toY
9268 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9271 if (fromX == toX && fromY == toY) return;
9273 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9274 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9275 if(gameInfo.variant == VariantKnightmate)
9276 king += (int) WhiteUnicorn - (int) WhiteKing;
9278 /* Code added by Tord: */
9279 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9280 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9281 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9282 board[fromY][fromX] = EmptySquare;
9283 board[toY][toX] = EmptySquare;
9284 if((toX > fromX) != (piece == WhiteRook)) {
9285 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9287 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9289 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9290 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9291 board[fromY][fromX] = EmptySquare;
9292 board[toY][toX] = EmptySquare;
9293 if((toX > fromX) != (piece == BlackRook)) {
9294 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9296 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9298 /* End of code added by Tord */
9300 } else if (board[fromY][fromX] == king
9301 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9302 && toY == fromY && toX > fromX+1) {
9303 board[fromY][fromX] = EmptySquare;
9304 board[toY][toX] = king;
9305 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9306 board[fromY][BOARD_RGHT-1] = EmptySquare;
9307 } else if (board[fromY][fromX] == king
9308 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9309 && toY == fromY && toX < fromX-1) {
9310 board[fromY][fromX] = EmptySquare;
9311 board[toY][toX] = king;
9312 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9313 board[fromY][BOARD_LEFT] = EmptySquare;
9314 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9315 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9316 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9318 /* white pawn promotion */
9319 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9320 if(gameInfo.variant==VariantBughouse ||
9321 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9322 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9323 board[fromY][fromX] = EmptySquare;
9324 } else if ((fromY >= BOARD_HEIGHT>>1)
9326 && gameInfo.variant != VariantXiangqi
9327 && gameInfo.variant != VariantBerolina
9328 && (board[fromY][fromX] == WhitePawn)
9329 && (board[toY][toX] == EmptySquare)) {
9330 board[fromY][fromX] = EmptySquare;
9331 board[toY][toX] = WhitePawn;
9332 captured = board[toY - 1][toX];
9333 board[toY - 1][toX] = EmptySquare;
9334 } else if ((fromY == BOARD_HEIGHT-4)
9336 && gameInfo.variant == VariantBerolina
9337 && (board[fromY][fromX] == WhitePawn)
9338 && (board[toY][toX] == EmptySquare)) {
9339 board[fromY][fromX] = EmptySquare;
9340 board[toY][toX] = WhitePawn;
9341 if(oldEP & EP_BEROLIN_A) {
9342 captured = board[fromY][fromX-1];
9343 board[fromY][fromX-1] = EmptySquare;
9344 }else{ captured = board[fromY][fromX+1];
9345 board[fromY][fromX+1] = EmptySquare;
9347 } else if (board[fromY][fromX] == king
9348 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9349 && toY == fromY && toX > fromX+1) {
9350 board[fromY][fromX] = EmptySquare;
9351 board[toY][toX] = king;
9352 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9353 board[fromY][BOARD_RGHT-1] = EmptySquare;
9354 } else if (board[fromY][fromX] == king
9355 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9356 && toY == fromY && toX < fromX-1) {
9357 board[fromY][fromX] = EmptySquare;
9358 board[toY][toX] = king;
9359 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9360 board[fromY][BOARD_LEFT] = EmptySquare;
9361 } else if (fromY == 7 && fromX == 3
9362 && board[fromY][fromX] == BlackKing
9363 && toY == 7 && toX == 5) {
9364 board[fromY][fromX] = EmptySquare;
9365 board[toY][toX] = BlackKing;
9366 board[fromY][7] = EmptySquare;
9367 board[toY][4] = BlackRook;
9368 } else if (fromY == 7 && fromX == 3
9369 && board[fromY][fromX] == BlackKing
9370 && toY == 7 && toX == 1) {
9371 board[fromY][fromX] = EmptySquare;
9372 board[toY][toX] = BlackKing;
9373 board[fromY][0] = EmptySquare;
9374 board[toY][2] = BlackRook;
9375 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9376 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9377 && toY < promoRank && promoChar
9379 /* black pawn promotion */
9380 board[toY][toX] = CharToPiece(ToLower(promoChar));
9381 if(gameInfo.variant==VariantBughouse ||
9382 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9383 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9384 board[fromY][fromX] = EmptySquare;
9385 } else if ((fromY < BOARD_HEIGHT>>1)
9387 && gameInfo.variant != VariantXiangqi
9388 && gameInfo.variant != VariantBerolina
9389 && (board[fromY][fromX] == BlackPawn)
9390 && (board[toY][toX] == EmptySquare)) {
9391 board[fromY][fromX] = EmptySquare;
9392 board[toY][toX] = BlackPawn;
9393 captured = board[toY + 1][toX];
9394 board[toY + 1][toX] = EmptySquare;
9395 } else if ((fromY == 3)
9397 && gameInfo.variant == VariantBerolina
9398 && (board[fromY][fromX] == BlackPawn)
9399 && (board[toY][toX] == EmptySquare)) {
9400 board[fromY][fromX] = EmptySquare;
9401 board[toY][toX] = BlackPawn;
9402 if(oldEP & EP_BEROLIN_A) {
9403 captured = board[fromY][fromX-1];
9404 board[fromY][fromX-1] = EmptySquare;
9405 }else{ captured = board[fromY][fromX+1];
9406 board[fromY][fromX+1] = EmptySquare;
9409 board[toY][toX] = board[fromY][fromX];
9410 board[fromY][fromX] = EmptySquare;
9414 if (gameInfo.holdingsWidth != 0) {
9416 /* !!A lot more code needs to be written to support holdings */
9417 /* [HGM] OK, so I have written it. Holdings are stored in the */
9418 /* penultimate board files, so they are automaticlly stored */
9419 /* in the game history. */
9420 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9421 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9422 /* Delete from holdings, by decreasing count */
9423 /* and erasing image if necessary */
9424 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9425 if(p < (int) BlackPawn) { /* white drop */
9426 p -= (int)WhitePawn;
9427 p = PieceToNumber((ChessSquare)p);
9428 if(p >= gameInfo.holdingsSize) p = 0;
9429 if(--board[p][BOARD_WIDTH-2] <= 0)
9430 board[p][BOARD_WIDTH-1] = EmptySquare;
9431 if((int)board[p][BOARD_WIDTH-2] < 0)
9432 board[p][BOARD_WIDTH-2] = 0;
9433 } else { /* black drop */
9434 p -= (int)BlackPawn;
9435 p = PieceToNumber((ChessSquare)p);
9436 if(p >= gameInfo.holdingsSize) p = 0;
9437 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9438 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9439 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9440 board[BOARD_HEIGHT-1-p][1] = 0;
9443 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9444 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9445 /* [HGM] holdings: Add to holdings, if holdings exist */
9446 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9447 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9448 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9451 if (p >= (int) BlackPawn) {
9452 p -= (int)BlackPawn;
9453 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9454 /* in Shogi restore piece to its original first */
9455 captured = (ChessSquare) (DEMOTED captured);
9458 p = PieceToNumber((ChessSquare)p);
9459 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9460 board[p][BOARD_WIDTH-2]++;
9461 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9463 p -= (int)WhitePawn;
9464 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9465 captured = (ChessSquare) (DEMOTED captured);
9468 p = PieceToNumber((ChessSquare)p);
9469 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9470 board[BOARD_HEIGHT-1-p][1]++;
9471 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9474 } else if (gameInfo.variant == VariantAtomic) {
9475 if (captured != EmptySquare) {
9477 for (y = toY-1; y <= toY+1; y++) {
9478 for (x = toX-1; x <= toX+1; x++) {
9479 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9480 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9481 board[y][x] = EmptySquare;
9485 board[toY][toX] = EmptySquare;
9488 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9489 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9491 if(promoChar == '+') {
9492 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9493 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9494 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9495 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9496 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9497 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9498 board[toY][toX] = newPiece;
9500 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9501 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9502 // [HGM] superchess: take promotion piece out of holdings
9503 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9504 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9505 if(!--board[k][BOARD_WIDTH-2])
9506 board[k][BOARD_WIDTH-1] = EmptySquare;
9508 if(!--board[BOARD_HEIGHT-1-k][1])
9509 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9515 /* Updates forwardMostMove */
9517 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9519 // forwardMostMove++; // [HGM] bare: moved downstream
9521 (void) CoordsToAlgebraic(boards[forwardMostMove],
9522 PosFlags(forwardMostMove),
9523 fromY, fromX, toY, toX, promoChar,
9524 parseList[forwardMostMove]);
9526 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9527 int timeLeft; static int lastLoadFlag=0; int king, piece;
9528 piece = boards[forwardMostMove][fromY][fromX];
9529 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9530 if(gameInfo.variant == VariantKnightmate)
9531 king += (int) WhiteUnicorn - (int) WhiteKing;
9532 if(forwardMostMove == 0) {
9533 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9534 fprintf(serverMoves, "%s;", UserName());
9535 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9536 fprintf(serverMoves, "%s;", second.tidy);
9537 fprintf(serverMoves, "%s;", first.tidy);
9538 if(gameMode == MachinePlaysWhite)
9539 fprintf(serverMoves, "%s;", UserName());
9540 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9541 fprintf(serverMoves, "%s;", second.tidy);
9542 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9543 lastLoadFlag = loadFlag;
9545 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9546 // print castling suffix
9547 if( toY == fromY && piece == king ) {
9549 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9551 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9554 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9555 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9556 boards[forwardMostMove][toY][toX] == EmptySquare
9557 && fromX != toX && fromY != toY)
9558 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9560 if(promoChar != NULLCHAR)
9561 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9563 char buf[MOVE_LEN*2], *p; int len;
9564 fprintf(serverMoves, "/%d/%d",
9565 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9566 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9567 else timeLeft = blackTimeRemaining/1000;
9568 fprintf(serverMoves, "/%d", timeLeft);
9569 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9570 if(p = strchr(buf, '=')) *p = NULLCHAR;
9571 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9572 fprintf(serverMoves, "/%s", buf);
9574 fflush(serverMoves);
9577 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9578 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9581 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9582 if (commentList[forwardMostMove+1] != NULL) {
9583 free(commentList[forwardMostMove+1]);
9584 commentList[forwardMostMove+1] = NULL;
9586 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9587 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9588 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9589 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9590 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9591 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9592 adjustedClock = FALSE;
9593 gameInfo.result = GameUnfinished;
9594 if (gameInfo.resultDetails != NULL) {
9595 free(gameInfo.resultDetails);
9596 gameInfo.resultDetails = NULL;
9598 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9599 moveList[forwardMostMove - 1]);
9600 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9606 if(gameInfo.variant != VariantShogi)
9607 strcat(parseList[forwardMostMove - 1], "+");
9611 strcat(parseList[forwardMostMove - 1], "#");
9617 /* Updates currentMove if not pausing */
9619 ShowMove (int fromX, int fromY, int toX, int toY)
9621 int instant = (gameMode == PlayFromGameFile) ?
9622 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9623 if(appData.noGUI) return;
9624 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9626 if (forwardMostMove == currentMove + 1) {
9627 AnimateMove(boards[forwardMostMove - 1],
9628 fromX, fromY, toX, toY);
9630 if (appData.highlightLastMove) {
9631 SetHighlights(fromX, fromY, toX, toY);
9634 currentMove = forwardMostMove;
9637 if (instant) return;
9639 DisplayMove(currentMove - 1);
9640 DrawPosition(FALSE, boards[currentMove]);
9641 DisplayBothClocks();
9642 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9646 SendEgtPath (ChessProgramState *cps)
9647 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9648 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9650 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9653 char c, *q = name+1, *r, *s;
9655 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9656 while(*p && *p != ',') *q++ = *p++;
9658 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9659 strcmp(name, ",nalimov:") == 0 ) {
9660 // take nalimov path from the menu-changeable option first, if it is defined
9661 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9662 SendToProgram(buf,cps); // send egtbpath command for nalimov
9664 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9665 (s = StrStr(appData.egtFormats, name)) != NULL) {
9666 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9667 s = r = StrStr(s, ":") + 1; // beginning of path info
9668 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9669 c = *r; *r = 0; // temporarily null-terminate path info
9670 *--q = 0; // strip of trailig ':' from name
9671 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9673 SendToProgram(buf,cps); // send egtbpath command for this format
9675 if(*p == ',') p++; // read away comma to position for next format name
9680 InitChessProgram (ChessProgramState *cps, int setup)
9681 /* setup needed to setup FRC opening position */
9683 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9684 if (appData.noChessProgram) return;
9685 hintRequested = FALSE;
9686 bookRequested = FALSE;
9688 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9689 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9690 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9691 if(cps->memSize) { /* [HGM] memory */
9692 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9693 SendToProgram(buf, cps);
9695 SendEgtPath(cps); /* [HGM] EGT */
9696 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9697 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9698 SendToProgram(buf, cps);
9701 SendToProgram(cps->initString, cps);
9702 if (gameInfo.variant != VariantNormal &&
9703 gameInfo.variant != VariantLoadable
9704 /* [HGM] also send variant if board size non-standard */
9705 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9707 char *v = VariantName(gameInfo.variant);
9708 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9709 /* [HGM] in protocol 1 we have to assume all variants valid */
9710 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9711 DisplayFatalError(buf, 0, 1);
9715 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9716 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9717 if( gameInfo.variant == VariantXiangqi )
9718 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9719 if( gameInfo.variant == VariantShogi )
9720 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9721 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9722 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9723 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9724 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9725 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9726 if( gameInfo.variant == VariantCourier )
9727 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9728 if( gameInfo.variant == VariantSuper )
9729 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9730 if( gameInfo.variant == VariantGreat )
9731 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9732 if( gameInfo.variant == VariantSChess )
9733 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9734 if( gameInfo.variant == VariantGrand )
9735 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9738 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9739 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9740 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9741 if(StrStr(cps->variants, b) == NULL) {
9742 // specific sized variant not known, check if general sizing allowed
9743 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9744 if(StrStr(cps->variants, "boardsize") == NULL) {
9745 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9746 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9747 DisplayFatalError(buf, 0, 1);
9750 /* [HGM] here we really should compare with the maximum supported board size */
9753 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9754 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9755 SendToProgram(buf, cps);
9757 currentlyInitializedVariant = gameInfo.variant;
9759 /* [HGM] send opening position in FRC to first engine */
9761 SendToProgram("force\n", cps);
9763 /* engine is now in force mode! Set flag to wake it up after first move. */
9764 setboardSpoiledMachineBlack = 1;
9768 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9769 SendToProgram(buf, cps);
9771 cps->maybeThinking = FALSE;
9772 cps->offeredDraw = 0;
9773 if (!appData.icsActive) {
9774 SendTimeControl(cps, movesPerSession, timeControl,
9775 timeIncrement, appData.searchDepth,
9778 if (appData.showThinking
9779 // [HGM] thinking: four options require thinking output to be sent
9780 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9782 SendToProgram("post\n", cps);
9784 SendToProgram("hard\n", cps);
9785 if (!appData.ponderNextMove) {
9786 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9787 it without being sure what state we are in first. "hard"
9788 is not a toggle, so that one is OK.
9790 SendToProgram("easy\n", cps);
9793 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9794 SendToProgram(buf, cps);
9796 cps->initDone = TRUE;
9797 ClearEngineOutputPane(cps == &second);
9802 StartChessProgram (ChessProgramState *cps)
9807 if (appData.noChessProgram) return;
9808 cps->initDone = FALSE;
9810 if (strcmp(cps->host, "localhost") == 0) {
9811 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9812 } else if (*appData.remoteShell == NULLCHAR) {
9813 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9815 if (*appData.remoteUser == NULLCHAR) {
9816 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9819 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9820 cps->host, appData.remoteUser, cps->program);
9822 err = StartChildProcess(buf, "", &cps->pr);
9826 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9827 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9828 if(cps != &first) return;
9829 appData.noChessProgram = TRUE;
9832 // DisplayFatalError(buf, err, 1);
9833 // cps->pr = NoProc;
9838 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9839 if (cps->protocolVersion > 1) {
9840 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9841 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9842 cps->comboCnt = 0; // and values of combo boxes
9843 SendToProgram(buf, cps);
9845 SendToProgram("xboard\n", cps);
9850 TwoMachinesEventIfReady P((void))
9852 static int curMess = 0;
9853 if (first.lastPing != first.lastPong) {
9854 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9855 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9858 if (second.lastPing != second.lastPong) {
9859 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9860 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9863 DisplayMessage("", ""); curMess = 0;
9869 MakeName (char *template)
9873 static char buf[MSG_SIZ];
9877 clock = time((time_t *)NULL);
9878 tm = localtime(&clock);
9880 while(*p++ = *template++) if(p[-1] == '%') {
9881 switch(*template++) {
9882 case 0: *p = 0; return buf;
9883 case 'Y': i = tm->tm_year+1900; break;
9884 case 'y': i = tm->tm_year-100; break;
9885 case 'M': i = tm->tm_mon+1; break;
9886 case 'd': i = tm->tm_mday; break;
9887 case 'h': i = tm->tm_hour; break;
9888 case 'm': i = tm->tm_min; break;
9889 case 's': i = tm->tm_sec; break;
9892 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9898 CountPlayers (char *p)
9901 while(p = strchr(p, '\n')) p++, n++; // count participants
9906 WriteTourneyFile (char *results, FILE *f)
9907 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9908 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9909 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9910 // create a file with tournament description
9911 fprintf(f, "-participants {%s}\n", appData.participants);
9912 fprintf(f, "-seedBase %d\n", appData.seedBase);
9913 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9914 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9915 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9916 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9917 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9918 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9919 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9920 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9921 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9922 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9923 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9924 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9926 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9928 fprintf(f, "-mps %d\n", appData.movesPerSession);
9929 fprintf(f, "-tc %s\n", appData.timeControl);
9930 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9932 fprintf(f, "-results \"%s\"\n", results);
9937 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9940 Substitute (char *participants, int expunge)
9942 int i, changed, changes=0, nPlayers=0;
9943 char *p, *q, *r, buf[MSG_SIZ];
9944 if(participants == NULL) return;
9945 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9946 r = p = participants; q = appData.participants;
9947 while(*p && *p == *q) {
9948 if(*p == '\n') r = p+1, nPlayers++;
9951 if(*p) { // difference
9952 while(*p && *p++ != '\n');
9953 while(*q && *q++ != '\n');
9955 changes = 1 + (strcmp(p, q) != 0);
9957 if(changes == 1) { // a single engine mnemonic was changed
9958 q = r; while(*q) nPlayers += (*q++ == '\n');
9959 p = buf; while(*r && (*p = *r++) != '\n') p++;
9961 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9962 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9963 if(mnemonic[i]) { // The substitute is valid
9965 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9966 flock(fileno(f), LOCK_EX);
9967 ParseArgsFromFile(f);
9968 fseek(f, 0, SEEK_SET);
9969 FREE(appData.participants); appData.participants = participants;
9970 if(expunge) { // erase results of replaced engine
9971 int len = strlen(appData.results), w, b, dummy;
9972 for(i=0; i<len; i++) {
9973 Pairing(i, nPlayers, &w, &b, &dummy);
9974 if((w == changed || b == changed) && appData.results[i] == '*') {
9975 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9980 for(i=0; i<len; i++) {
9981 Pairing(i, nPlayers, &w, &b, &dummy);
9982 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9985 WriteTourneyFile(appData.results, f);
9986 fclose(f); // release lock
9989 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9991 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9992 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9998 CreateTourney (char *name)
10001 if(matchMode && strcmp(name, appData.tourneyFile)) {
10002 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10004 if(name[0] == NULLCHAR) {
10005 if(appData.participants[0])
10006 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10009 f = fopen(name, "r");
10010 if(f) { // file exists
10011 ASSIGN(appData.tourneyFile, name);
10012 ParseArgsFromFile(f); // parse it
10014 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10015 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10016 DisplayError(_("Not enough participants"), 0);
10019 ASSIGN(appData.tourneyFile, name);
10020 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10021 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10024 appData.noChessProgram = FALSE;
10025 appData.clockMode = TRUE;
10031 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10033 char buf[MSG_SIZ], *p, *q;
10034 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10035 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10036 skip = !all && group[0]; // if group requested, we start in skip mode
10037 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10038 p = names; q = buf; header = 0;
10039 while(*p && *p != '\n') *q++ = *p++;
10041 if(*p == '\n') p++;
10042 if(buf[0] == '#') {
10043 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10044 depth++; // we must be entering a new group
10045 if(all) continue; // suppress printing group headers when complete list requested
10047 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10049 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10050 if(engineList[i]) free(engineList[i]);
10051 engineList[i] = strdup(buf);
10052 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10053 if(engineMnemonic[i]) free(engineMnemonic[i]);
10054 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10056 sscanf(q + 8, "%s", buf + strlen(buf));
10059 engineMnemonic[i] = strdup(buf);
10062 engineList[i] = engineMnemonic[i] = NULL;
10066 // following implemented as macro to avoid type limitations
10067 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10070 SwapEngines (int n)
10071 { // swap settings for first engine and other engine (so far only some selected options)
10076 SWAP(chessProgram, p)
10078 SWAP(hasOwnBookUCI, h)
10079 SWAP(protocolVersion, h)
10081 SWAP(scoreIsAbsolute, h)
10086 SWAP(engOptions, p)
10087 SWAP(engInitString, p)
10088 SWAP(computerString, p)
10090 SWAP(fenOverride, p)
10092 SWAP(accumulateTC, h)
10097 SetPlayer (int player, char *p)
10098 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10100 char buf[MSG_SIZ], *engineName;
10101 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10102 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10103 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10105 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10106 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10107 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10108 ParseArgsFromString(buf);
10114 char *recentEngines;
10117 RecentEngineEvent (int nr)
10120 // SwapEngines(1); // bump first to second
10121 // ReplaceEngine(&second, 1); // and load it there
10122 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10123 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10124 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10125 ReplaceEngine(&first, 0);
10126 FloatToFront(&appData.recentEngineList, command[n]);
10131 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10132 { // determine players from game number
10133 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10135 if(appData.tourneyType == 0) {
10136 roundsPerCycle = (nPlayers - 1) | 1;
10137 pairingsPerRound = nPlayers / 2;
10138 } else if(appData.tourneyType > 0) {
10139 roundsPerCycle = nPlayers - appData.tourneyType;
10140 pairingsPerRound = appData.tourneyType;
10142 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10143 gamesPerCycle = gamesPerRound * roundsPerCycle;
10144 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10145 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10146 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10147 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10148 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10149 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10151 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10152 if(appData.roundSync) *syncInterval = gamesPerRound;
10154 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10156 if(appData.tourneyType == 0) {
10157 if(curPairing == (nPlayers-1)/2 ) {
10158 *whitePlayer = curRound;
10159 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10161 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10162 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10163 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10164 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10166 } else if(appData.tourneyType > 1) {
10167 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10168 *whitePlayer = curRound + appData.tourneyType;
10169 } else if(appData.tourneyType > 0) {
10170 *whitePlayer = curPairing;
10171 *blackPlayer = curRound + appData.tourneyType;
10174 // take care of white/black alternation per round.
10175 // For cycles and games this is already taken care of by default, derived from matchGame!
10176 return curRound & 1;
10180 NextTourneyGame (int nr, int *swapColors)
10181 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10183 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10185 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10186 tf = fopen(appData.tourneyFile, "r");
10187 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10188 ParseArgsFromFile(tf); fclose(tf);
10189 InitTimeControls(); // TC might be altered from tourney file
10191 nPlayers = CountPlayers(appData.participants); // count participants
10192 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10193 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10196 p = q = appData.results;
10197 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10198 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10199 DisplayMessage(_("Waiting for other game(s)"),"");
10200 waitingForGame = TRUE;
10201 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10204 waitingForGame = FALSE;
10207 if(appData.tourneyType < 0) {
10208 if(nr>=0 && !pairingReceived) {
10210 if(pairing.pr == NoProc) {
10211 if(!appData.pairingEngine[0]) {
10212 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10215 StartChessProgram(&pairing); // starts the pairing engine
10217 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10218 SendToProgram(buf, &pairing);
10219 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10220 SendToProgram(buf, &pairing);
10221 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10223 pairingReceived = 0; // ... so we continue here
10225 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10226 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10227 matchGame = 1; roundNr = nr / syncInterval + 1;
10230 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10232 // redefine engines, engine dir, etc.
10233 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10234 if(first.pr == NoProc) {
10235 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10236 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10238 if(second.pr == NoProc) {
10240 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10241 SwapEngines(1); // and make that valid for second engine by swapping
10242 InitEngine(&second, 1);
10244 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10245 UpdateLogos(FALSE); // leave display to ModeHiglight()
10251 { // performs game initialization that does not invoke engines, and then tries to start the game
10252 int res, firstWhite, swapColors = 0;
10253 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10254 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
10256 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10257 if(strcmp(buf, currentDebugFile)) { // name has changed
10258 FILE *f = fopen(buf, "w");
10259 if(f) { // if opening the new file failed, just keep using the old one
10260 ASSIGN(currentDebugFile, buf);
10264 if(appData.serverFileName) {
10265 if(serverFP) fclose(serverFP);
10266 serverFP = fopen(appData.serverFileName, "w");
10267 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10268 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10272 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10273 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10274 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10275 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10276 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10277 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10278 Reset(FALSE, first.pr != NoProc);
10279 res = LoadGameOrPosition(matchGame); // setup game
10280 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10281 if(!res) return; // abort when bad game/pos file
10282 TwoMachinesEvent();
10286 UserAdjudicationEvent (int result)
10288 ChessMove gameResult = GameIsDrawn;
10291 gameResult = WhiteWins;
10293 else if( result < 0 ) {
10294 gameResult = BlackWins;
10297 if( gameMode == TwoMachinesPlay ) {
10298 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10303 // [HGM] save: calculate checksum of game to make games easily identifiable
10305 StringCheckSum (char *s)
10308 if(s==NULL) return 0;
10309 while(*s) i = i*259 + *s++;
10317 for(i=backwardMostMove; i<forwardMostMove; i++) {
10318 sum += pvInfoList[i].depth;
10319 sum += StringCheckSum(parseList[i]);
10320 sum += StringCheckSum(commentList[i]);
10323 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10324 return sum + StringCheckSum(commentList[i]);
10325 } // end of save patch
10328 GameEnds (ChessMove result, char *resultDetails, int whosays)
10330 GameMode nextGameMode;
10332 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10334 if(endingGame) return; /* [HGM] crash: forbid recursion */
10336 if(twoBoards) { // [HGM] dual: switch back to one board
10337 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10338 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10340 if (appData.debugMode) {
10341 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10342 result, resultDetails ? resultDetails : "(null)", whosays);
10345 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10347 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10348 /* If we are playing on ICS, the server decides when the
10349 game is over, but the engine can offer to draw, claim
10353 if (appData.zippyPlay && first.initDone) {
10354 if (result == GameIsDrawn) {
10355 /* In case draw still needs to be claimed */
10356 SendToICS(ics_prefix);
10357 SendToICS("draw\n");
10358 } else if (StrCaseStr(resultDetails, "resign")) {
10359 SendToICS(ics_prefix);
10360 SendToICS("resign\n");
10364 endingGame = 0; /* [HGM] crash */
10368 /* If we're loading the game from a file, stop */
10369 if (whosays == GE_FILE) {
10370 (void) StopLoadGameTimer();
10374 /* Cancel draw offers */
10375 first.offeredDraw = second.offeredDraw = 0;
10377 /* If this is an ICS game, only ICS can really say it's done;
10378 if not, anyone can. */
10379 isIcsGame = (gameMode == IcsPlayingWhite ||
10380 gameMode == IcsPlayingBlack ||
10381 gameMode == IcsObserving ||
10382 gameMode == IcsExamining);
10384 if (!isIcsGame || whosays == GE_ICS) {
10385 /* OK -- not an ICS game, or ICS said it was done */
10387 if (!isIcsGame && !appData.noChessProgram)
10388 SetUserThinkingEnables();
10390 /* [HGM] if a machine claims the game end we verify this claim */
10391 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10392 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10394 ChessMove trueResult = (ChessMove) -1;
10396 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10397 first.twoMachinesColor[0] :
10398 second.twoMachinesColor[0] ;
10400 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10401 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10402 /* [HGM] verify: engine mate claims accepted if they were flagged */
10403 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10405 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10406 /* [HGM] verify: engine mate claims accepted if they were flagged */
10407 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10409 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10410 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10413 // now verify win claims, but not in drop games, as we don't understand those yet
10414 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10415 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10416 (result == WhiteWins && claimer == 'w' ||
10417 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10418 if (appData.debugMode) {
10419 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10420 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10422 if(result != trueResult) {
10423 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10424 result = claimer == 'w' ? BlackWins : WhiteWins;
10425 resultDetails = buf;
10428 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10429 && (forwardMostMove <= backwardMostMove ||
10430 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10431 (claimer=='b')==(forwardMostMove&1))
10433 /* [HGM] verify: draws that were not flagged are false claims */
10434 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10435 result = claimer == 'w' ? BlackWins : WhiteWins;
10436 resultDetails = buf;
10438 /* (Claiming a loss is accepted no questions asked!) */
10440 /* [HGM] bare: don't allow bare King to win */
10441 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10442 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10443 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10444 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10445 && result != GameIsDrawn)
10446 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10447 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10448 int p = (signed char)boards[forwardMostMove][i][j] - color;
10449 if(p >= 0 && p <= (int)WhiteKing) k++;
10451 if (appData.debugMode) {
10452 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10453 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10456 result = GameIsDrawn;
10457 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10458 resultDetails = buf;
10464 if(serverMoves != NULL && !loadFlag) { char c = '=';
10465 if(result==WhiteWins) c = '+';
10466 if(result==BlackWins) c = '-';
10467 if(resultDetails != NULL)
10468 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10470 if (resultDetails != NULL) {
10471 gameInfo.result = result;
10472 gameInfo.resultDetails = StrSave(resultDetails);
10474 /* display last move only if game was not loaded from file */
10475 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10476 DisplayMove(currentMove - 1);
10478 if (forwardMostMove != 0) {
10479 if (gameMode != PlayFromGameFile && gameMode != EditGame
10480 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10482 if (*appData.saveGameFile != NULLCHAR) {
10483 SaveGameToFile(appData.saveGameFile, TRUE);
10484 } else if (appData.autoSaveGames) {
10487 if (*appData.savePositionFile != NULLCHAR) {
10488 SavePositionToFile(appData.savePositionFile);
10493 /* Tell program how game ended in case it is learning */
10494 /* [HGM] Moved this to after saving the PGN, just in case */
10495 /* engine died and we got here through time loss. In that */
10496 /* case we will get a fatal error writing the pipe, which */
10497 /* would otherwise lose us the PGN. */
10498 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10499 /* output during GameEnds should never be fatal anymore */
10500 if (gameMode == MachinePlaysWhite ||
10501 gameMode == MachinePlaysBlack ||
10502 gameMode == TwoMachinesPlay ||
10503 gameMode == IcsPlayingWhite ||
10504 gameMode == IcsPlayingBlack ||
10505 gameMode == BeginningOfGame) {
10507 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10509 if (first.pr != NoProc) {
10510 SendToProgram(buf, &first);
10512 if (second.pr != NoProc &&
10513 gameMode == TwoMachinesPlay) {
10514 SendToProgram(buf, &second);
10519 if (appData.icsActive) {
10520 if (appData.quietPlay &&
10521 (gameMode == IcsPlayingWhite ||
10522 gameMode == IcsPlayingBlack)) {
10523 SendToICS(ics_prefix);
10524 SendToICS("set shout 1\n");
10526 nextGameMode = IcsIdle;
10527 ics_user_moved = FALSE;
10528 /* clean up premove. It's ugly when the game has ended and the
10529 * premove highlights are still on the board.
10532 gotPremove = FALSE;
10533 ClearPremoveHighlights();
10534 DrawPosition(FALSE, boards[currentMove]);
10536 if (whosays == GE_ICS) {
10539 if (gameMode == IcsPlayingWhite)
10541 else if(gameMode == IcsPlayingBlack)
10542 PlayIcsLossSound();
10545 if (gameMode == IcsPlayingBlack)
10547 else if(gameMode == IcsPlayingWhite)
10548 PlayIcsLossSound();
10551 PlayIcsDrawSound();
10554 PlayIcsUnfinishedSound();
10557 } else if (gameMode == EditGame ||
10558 gameMode == PlayFromGameFile ||
10559 gameMode == AnalyzeMode ||
10560 gameMode == AnalyzeFile) {
10561 nextGameMode = gameMode;
10563 nextGameMode = EndOfGame;
10568 nextGameMode = gameMode;
10571 if (appData.noChessProgram) {
10572 gameMode = nextGameMode;
10574 endingGame = 0; /* [HGM] crash */
10579 /* Put first chess program into idle state */
10580 if (first.pr != NoProc &&
10581 (gameMode == MachinePlaysWhite ||
10582 gameMode == MachinePlaysBlack ||
10583 gameMode == TwoMachinesPlay ||
10584 gameMode == IcsPlayingWhite ||
10585 gameMode == IcsPlayingBlack ||
10586 gameMode == BeginningOfGame)) {
10587 SendToProgram("force\n", &first);
10588 if (first.usePing) {
10590 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10591 SendToProgram(buf, &first);
10594 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10595 /* Kill off first chess program */
10596 if (first.isr != NULL)
10597 RemoveInputSource(first.isr);
10600 if (first.pr != NoProc) {
10602 DoSleep( appData.delayBeforeQuit );
10603 SendToProgram("quit\n", &first);
10604 DoSleep( appData.delayAfterQuit );
10605 DestroyChildProcess(first.pr, first.useSigterm);
10609 if (second.reuse) {
10610 /* Put second chess program into idle state */
10611 if (second.pr != NoProc &&
10612 gameMode == TwoMachinesPlay) {
10613 SendToProgram("force\n", &second);
10614 if (second.usePing) {
10616 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10617 SendToProgram(buf, &second);
10620 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10621 /* Kill off second chess program */
10622 if (second.isr != NULL)
10623 RemoveInputSource(second.isr);
10626 if (second.pr != NoProc) {
10627 DoSleep( appData.delayBeforeQuit );
10628 SendToProgram("quit\n", &second);
10629 DoSleep( appData.delayAfterQuit );
10630 DestroyChildProcess(second.pr, second.useSigterm);
10632 second.pr = NoProc;
10635 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10636 char resChar = '=';
10640 if (first.twoMachinesColor[0] == 'w') {
10643 second.matchWins++;
10648 if (first.twoMachinesColor[0] == 'b') {
10651 second.matchWins++;
10654 case GameUnfinished:
10660 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10661 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10662 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10663 ReserveGame(nextGame, resChar); // sets nextGame
10664 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10665 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10666 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10668 if (nextGame <= appData.matchGames && !abortMatch) {
10669 gameMode = nextGameMode;
10670 matchGame = nextGame; // this will be overruled in tourney mode!
10671 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10672 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10673 endingGame = 0; /* [HGM] crash */
10676 gameMode = nextGameMode;
10677 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10678 first.tidy, second.tidy,
10679 first.matchWins, second.matchWins,
10680 appData.matchGames - (first.matchWins + second.matchWins));
10681 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10682 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10683 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10684 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10685 first.twoMachinesColor = "black\n";
10686 second.twoMachinesColor = "white\n";
10688 first.twoMachinesColor = "white\n";
10689 second.twoMachinesColor = "black\n";
10693 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10694 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10696 gameMode = nextGameMode;
10698 endingGame = 0; /* [HGM] crash */
10699 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10700 if(matchMode == TRUE) { // match through command line: exit with or without popup
10702 ToNrEvent(forwardMostMove);
10703 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10705 } else DisplayFatalError(buf, 0, 0);
10706 } else { // match through menu; just stop, with or without popup
10707 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10710 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10711 } else DisplayNote(buf);
10713 if(ranking) free(ranking);
10717 /* Assumes program was just initialized (initString sent).
10718 Leaves program in force mode. */
10720 FeedMovesToProgram (ChessProgramState *cps, int upto)
10724 if (appData.debugMode)
10725 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10726 startedFromSetupPosition ? "position and " : "",
10727 backwardMostMove, upto, cps->which);
10728 if(currentlyInitializedVariant != gameInfo.variant) {
10730 // [HGM] variantswitch: make engine aware of new variant
10731 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10732 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10733 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10734 SendToProgram(buf, cps);
10735 currentlyInitializedVariant = gameInfo.variant;
10737 SendToProgram("force\n", cps);
10738 if (startedFromSetupPosition) {
10739 SendBoard(cps, backwardMostMove);
10740 if (appData.debugMode) {
10741 fprintf(debugFP, "feedMoves\n");
10744 for (i = backwardMostMove; i < upto; i++) {
10745 SendMoveToProgram(i, cps);
10751 ResurrectChessProgram ()
10753 /* The chess program may have exited.
10754 If so, restart it and feed it all the moves made so far. */
10755 static int doInit = 0;
10757 if (appData.noChessProgram) return 1;
10759 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10760 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10761 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10762 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10764 if (first.pr != NoProc) return 1;
10765 StartChessProgram(&first);
10767 InitChessProgram(&first, FALSE);
10768 FeedMovesToProgram(&first, currentMove);
10770 if (!first.sendTime) {
10771 /* can't tell gnuchess what its clock should read,
10772 so we bow to its notion. */
10774 timeRemaining[0][currentMove] = whiteTimeRemaining;
10775 timeRemaining[1][currentMove] = blackTimeRemaining;
10778 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10779 appData.icsEngineAnalyze) && first.analysisSupport) {
10780 SendToProgram("analyze\n", &first);
10781 first.analyzing = TRUE;
10787 * Button procedures
10790 Reset (int redraw, int init)
10794 if (appData.debugMode) {
10795 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10796 redraw, init, gameMode);
10798 CleanupTail(); // [HGM] vari: delete any stored variations
10799 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10800 pausing = pauseExamInvalid = FALSE;
10801 startedFromSetupPosition = blackPlaysFirst = FALSE;
10803 whiteFlag = blackFlag = FALSE;
10804 userOfferedDraw = FALSE;
10805 hintRequested = bookRequested = FALSE;
10806 first.maybeThinking = FALSE;
10807 second.maybeThinking = FALSE;
10808 first.bookSuspend = FALSE; // [HGM] book
10809 second.bookSuspend = FALSE;
10810 thinkOutput[0] = NULLCHAR;
10811 lastHint[0] = NULLCHAR;
10812 ClearGameInfo(&gameInfo);
10813 gameInfo.variant = StringToVariant(appData.variant);
10814 ics_user_moved = ics_clock_paused = FALSE;
10815 ics_getting_history = H_FALSE;
10817 white_holding[0] = black_holding[0] = NULLCHAR;
10818 ClearProgramStats();
10819 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10823 flipView = appData.flipView;
10824 ClearPremoveHighlights();
10825 gotPremove = FALSE;
10826 alarmSounded = FALSE;
10828 GameEnds(EndOfFile, NULL, GE_PLAYER);
10829 if(appData.serverMovesName != NULL) {
10830 /* [HGM] prepare to make moves file for broadcasting */
10831 clock_t t = clock();
10832 if(serverMoves != NULL) fclose(serverMoves);
10833 serverMoves = fopen(appData.serverMovesName, "r");
10834 if(serverMoves != NULL) {
10835 fclose(serverMoves);
10836 /* delay 15 sec before overwriting, so all clients can see end */
10837 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10839 serverMoves = fopen(appData.serverMovesName, "w");
10843 gameMode = BeginningOfGame;
10845 if(appData.icsActive) gameInfo.variant = VariantNormal;
10846 currentMove = forwardMostMove = backwardMostMove = 0;
10847 MarkTargetSquares(1);
10848 InitPosition(redraw);
10849 for (i = 0; i < MAX_MOVES; i++) {
10850 if (commentList[i] != NULL) {
10851 free(commentList[i]);
10852 commentList[i] = NULL;
10856 timeRemaining[0][0] = whiteTimeRemaining;
10857 timeRemaining[1][0] = blackTimeRemaining;
10859 if (first.pr == NoProc) {
10860 StartChessProgram(&first);
10863 InitChessProgram(&first, startedFromSetupPosition);
10866 DisplayMessage("", "");
10867 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10868 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10869 ClearMap(); // [HGM] exclude: invalidate map
10873 AutoPlayGameLoop ()
10876 if (!AutoPlayOneMove())
10878 if (matchMode || appData.timeDelay == 0)
10880 if (appData.timeDelay < 0)
10882 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10891 int fromX, fromY, toX, toY;
10893 if (appData.debugMode) {
10894 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10897 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10900 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10901 pvInfoList[currentMove].depth = programStats.depth;
10902 pvInfoList[currentMove].score = programStats.score;
10903 pvInfoList[currentMove].time = 0;
10904 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10907 if (currentMove >= forwardMostMove) {
10908 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10909 // gameMode = EndOfGame;
10910 // ModeHighlight();
10912 /* [AS] Clear current move marker at the end of a game */
10913 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10918 toX = moveList[currentMove][2] - AAA;
10919 toY = moveList[currentMove][3] - ONE;
10921 if (moveList[currentMove][1] == '@') {
10922 if (appData.highlightLastMove) {
10923 SetHighlights(-1, -1, toX, toY);
10926 fromX = moveList[currentMove][0] - AAA;
10927 fromY = moveList[currentMove][1] - ONE;
10929 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10931 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10933 if (appData.highlightLastMove) {
10934 SetHighlights(fromX, fromY, toX, toY);
10937 DisplayMove(currentMove);
10938 SendMoveToProgram(currentMove++, &first);
10939 DisplayBothClocks();
10940 DrawPosition(FALSE, boards[currentMove]);
10941 // [HGM] PV info: always display, routine tests if empty
10942 DisplayComment(currentMove - 1, commentList[currentMove]);
10948 LoadGameOneMove (ChessMove readAhead)
10950 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10951 char promoChar = NULLCHAR;
10952 ChessMove moveType;
10953 char move[MSG_SIZ];
10956 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10957 gameMode != AnalyzeMode && gameMode != Training) {
10962 yyboardindex = forwardMostMove;
10963 if (readAhead != EndOfFile) {
10964 moveType = readAhead;
10966 if (gameFileFP == NULL)
10968 moveType = (ChessMove) Myylex();
10972 switch (moveType) {
10974 if (appData.debugMode)
10975 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10978 /* append the comment but don't display it */
10979 AppendComment(currentMove, p, FALSE);
10982 case WhiteCapturesEnPassant:
10983 case BlackCapturesEnPassant:
10984 case WhitePromotion:
10985 case BlackPromotion:
10986 case WhiteNonPromotion:
10987 case BlackNonPromotion:
10989 case WhiteKingSideCastle:
10990 case WhiteQueenSideCastle:
10991 case BlackKingSideCastle:
10992 case BlackQueenSideCastle:
10993 case WhiteKingSideCastleWild:
10994 case WhiteQueenSideCastleWild:
10995 case BlackKingSideCastleWild:
10996 case BlackQueenSideCastleWild:
10998 case WhiteHSideCastleFR:
10999 case WhiteASideCastleFR:
11000 case BlackHSideCastleFR:
11001 case BlackASideCastleFR:
11003 if (appData.debugMode)
11004 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11005 fromX = currentMoveString[0] - AAA;
11006 fromY = currentMoveString[1] - ONE;
11007 toX = currentMoveString[2] - AAA;
11008 toY = currentMoveString[3] - ONE;
11009 promoChar = currentMoveString[4];
11014 if (appData.debugMode)
11015 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11016 fromX = moveType == WhiteDrop ?
11017 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11018 (int) CharToPiece(ToLower(currentMoveString[0]));
11020 toX = currentMoveString[2] - AAA;
11021 toY = currentMoveString[3] - ONE;
11027 case GameUnfinished:
11028 if (appData.debugMode)
11029 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11030 p = strchr(yy_text, '{');
11031 if (p == NULL) p = strchr(yy_text, '(');
11034 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11036 q = strchr(p, *p == '{' ? '}' : ')');
11037 if (q != NULL) *q = NULLCHAR;
11040 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11041 GameEnds(moveType, p, GE_FILE);
11043 if (cmailMsgLoaded) {
11045 flipView = WhiteOnMove(currentMove);
11046 if (moveType == GameUnfinished) flipView = !flipView;
11047 if (appData.debugMode)
11048 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11053 if (appData.debugMode)
11054 fprintf(debugFP, "Parser hit end of file\n");
11055 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11061 if (WhiteOnMove(currentMove)) {
11062 GameEnds(BlackWins, "Black mates", GE_FILE);
11064 GameEnds(WhiteWins, "White mates", GE_FILE);
11068 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11074 case MoveNumberOne:
11075 if (lastLoadGameStart == GNUChessGame) {
11076 /* GNUChessGames have numbers, but they aren't move numbers */
11077 if (appData.debugMode)
11078 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11079 yy_text, (int) moveType);
11080 return LoadGameOneMove(EndOfFile); /* tail recursion */
11082 /* else fall thru */
11087 /* Reached start of next game in file */
11088 if (appData.debugMode)
11089 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11090 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11096 if (WhiteOnMove(currentMove)) {
11097 GameEnds(BlackWins, "Black mates", GE_FILE);
11099 GameEnds(WhiteWins, "White mates", GE_FILE);
11103 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11109 case PositionDiagram: /* should not happen; ignore */
11110 case ElapsedTime: /* ignore */
11111 case NAG: /* ignore */
11112 if (appData.debugMode)
11113 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11114 yy_text, (int) moveType);
11115 return LoadGameOneMove(EndOfFile); /* tail recursion */
11118 if (appData.testLegality) {
11119 if (appData.debugMode)
11120 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11121 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11122 (forwardMostMove / 2) + 1,
11123 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11124 DisplayError(move, 0);
11127 if (appData.debugMode)
11128 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11129 yy_text, currentMoveString);
11130 fromX = currentMoveString[0] - AAA;
11131 fromY = currentMoveString[1] - ONE;
11132 toX = currentMoveString[2] - AAA;
11133 toY = currentMoveString[3] - ONE;
11134 promoChar = currentMoveString[4];
11138 case AmbiguousMove:
11139 if (appData.debugMode)
11140 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11141 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11142 (forwardMostMove / 2) + 1,
11143 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11144 DisplayError(move, 0);
11149 case ImpossibleMove:
11150 if (appData.debugMode)
11151 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11152 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11153 (forwardMostMove / 2) + 1,
11154 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11155 DisplayError(move, 0);
11161 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11162 DrawPosition(FALSE, boards[currentMove]);
11163 DisplayBothClocks();
11164 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11165 DisplayComment(currentMove - 1, commentList[currentMove]);
11167 (void) StopLoadGameTimer();
11169 cmailOldMove = forwardMostMove;
11172 /* currentMoveString is set as a side-effect of yylex */
11174 thinkOutput[0] = NULLCHAR;
11175 MakeMove(fromX, fromY, toX, toY, promoChar);
11176 currentMove = forwardMostMove;
11181 /* Load the nth game from the given file */
11183 LoadGameFromFile (char *filename, int n, char *title, int useList)
11188 if (strcmp(filename, "-") == 0) {
11192 f = fopen(filename, "rb");
11194 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11195 DisplayError(buf, errno);
11199 if (fseek(f, 0, 0) == -1) {
11200 /* f is not seekable; probably a pipe */
11203 if (useList && n == 0) {
11204 int error = GameListBuild(f);
11206 DisplayError(_("Cannot build game list"), error);
11207 } else if (!ListEmpty(&gameList) &&
11208 ((ListGame *) gameList.tailPred)->number > 1) {
11209 GameListPopUp(f, title);
11216 return LoadGame(f, n, title, FALSE);
11221 MakeRegisteredMove ()
11223 int fromX, fromY, toX, toY;
11225 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11226 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11229 if (appData.debugMode)
11230 fprintf(debugFP, "Restoring %s for game %d\n",
11231 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11233 thinkOutput[0] = NULLCHAR;
11234 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11235 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11236 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11237 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11238 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11239 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11240 MakeMove(fromX, fromY, toX, toY, promoChar);
11241 ShowMove(fromX, fromY, toX, toY);
11243 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11250 if (WhiteOnMove(currentMove)) {
11251 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11253 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11258 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11265 if (WhiteOnMove(currentMove)) {
11266 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11268 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11273 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11284 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11286 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11290 if (gameNumber > nCmailGames) {
11291 DisplayError(_("No more games in this message"), 0);
11294 if (f == lastLoadGameFP) {
11295 int offset = gameNumber - lastLoadGameNumber;
11297 cmailMsg[0] = NULLCHAR;
11298 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11299 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11300 nCmailMovesRegistered--;
11302 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11303 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11304 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11307 if (! RegisterMove()) return FALSE;
11311 retVal = LoadGame(f, gameNumber, title, useList);
11313 /* Make move registered during previous look at this game, if any */
11314 MakeRegisteredMove();
11316 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11317 commentList[currentMove]
11318 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11319 DisplayComment(currentMove - 1, commentList[currentMove]);
11325 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11327 ReloadGame (int offset)
11329 int gameNumber = lastLoadGameNumber + offset;
11330 if (lastLoadGameFP == NULL) {
11331 DisplayError(_("No game has been loaded yet"), 0);
11334 if (gameNumber <= 0) {
11335 DisplayError(_("Can't back up any further"), 0);
11338 if (cmailMsgLoaded) {
11339 return CmailLoadGame(lastLoadGameFP, gameNumber,
11340 lastLoadGameTitle, lastLoadGameUseList);
11342 return LoadGame(lastLoadGameFP, gameNumber,
11343 lastLoadGameTitle, lastLoadGameUseList);
11347 int keys[EmptySquare+1];
11350 PositionMatches (Board b1, Board b2)
11353 switch(appData.searchMode) {
11354 case 1: return CompareWithRights(b1, b2);
11356 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11361 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11362 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11363 sum += keys[b1[r][f]] - keys[b2[r][f]];
11367 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11368 sum += keys[b1[r][f]] - keys[b2[r][f]];
11380 int pieceList[256], quickBoard[256];
11381 ChessSquare pieceType[256] = { EmptySquare };
11382 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11383 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11384 int soughtTotal, turn;
11385 Boolean epOK, flipSearch;
11388 unsigned char piece, to;
11391 #define DSIZE (250000)
11393 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11394 Move *moveDatabase = initialSpace;
11395 unsigned int movePtr, dataSize = DSIZE;
11398 MakePieceList (Board board, int *counts)
11400 int r, f, n=Q_PROMO, total=0;
11401 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11402 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11403 int sq = f + (r<<4);
11404 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11405 quickBoard[sq] = ++n;
11407 pieceType[n] = board[r][f];
11408 counts[board[r][f]]++;
11409 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11410 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11414 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11419 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11421 int sq = fromX + (fromY<<4);
11422 int piece = quickBoard[sq];
11423 quickBoard[sq] = 0;
11424 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11425 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11426 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11427 moveDatabase[movePtr++].piece = Q_WCASTL;
11428 quickBoard[sq] = piece;
11429 piece = quickBoard[from]; quickBoard[from] = 0;
11430 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11432 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11433 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11434 moveDatabase[movePtr++].piece = Q_BCASTL;
11435 quickBoard[sq] = piece;
11436 piece = quickBoard[from]; quickBoard[from] = 0;
11437 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11439 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11440 quickBoard[(fromY<<4)+toX] = 0;
11441 moveDatabase[movePtr].piece = Q_EP;
11442 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11443 moveDatabase[movePtr].to = sq;
11445 if(promoPiece != pieceType[piece]) {
11446 moveDatabase[movePtr++].piece = Q_PROMO;
11447 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11449 moveDatabase[movePtr].piece = piece;
11450 quickBoard[sq] = piece;
11455 PackGame (Board board)
11457 Move *newSpace = NULL;
11458 moveDatabase[movePtr].piece = 0; // terminate previous game
11459 if(movePtr > dataSize) {
11460 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11461 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11462 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11465 Move *p = moveDatabase, *q = newSpace;
11466 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11467 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11468 moveDatabase = newSpace;
11469 } else { // calloc failed, we must be out of memory. Too bad...
11470 dataSize = 0; // prevent calloc events for all subsequent games
11471 return 0; // and signal this one isn't cached
11475 MakePieceList(board, counts);
11480 QuickCompare (Board board, int *minCounts, int *maxCounts)
11481 { // compare according to search mode
11483 switch(appData.searchMode)
11485 case 1: // exact position match
11486 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11487 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11488 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11491 case 2: // can have extra material on empty squares
11492 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11493 if(board[r][f] == EmptySquare) continue;
11494 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11497 case 3: // material with exact Pawn structure
11498 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11499 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11500 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11501 } // fall through to material comparison
11502 case 4: // exact material
11503 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11505 case 6: // material range with given imbalance
11506 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11507 // fall through to range comparison
11508 case 5: // material range
11509 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11515 QuickScan (Board board, Move *move)
11516 { // reconstruct game,and compare all positions in it
11517 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11519 int piece = move->piece;
11520 int to = move->to, from = pieceList[piece];
11521 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11522 if(!piece) return -1;
11523 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11524 piece = (++move)->piece;
11525 from = pieceList[piece];
11526 counts[pieceType[piece]]--;
11527 pieceType[piece] = (ChessSquare) move->to;
11528 counts[move->to]++;
11529 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11530 counts[pieceType[quickBoard[to]]]--;
11531 quickBoard[to] = 0; total--;
11534 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11535 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11536 from = pieceList[piece]; // so this must be King
11537 quickBoard[from] = 0;
11538 quickBoard[to] = piece;
11539 pieceList[piece] = to;
11544 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11545 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11546 quickBoard[from] = 0;
11547 quickBoard[to] = piece;
11548 pieceList[piece] = to;
11550 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11551 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11552 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11553 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11555 static int lastCounts[EmptySquare+1];
11557 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11558 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11559 } else stretch = 0;
11560 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11569 flipSearch = FALSE;
11570 CopyBoard(soughtBoard, boards[currentMove]);
11571 soughtTotal = MakePieceList(soughtBoard, maxSought);
11572 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11573 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11574 CopyBoard(reverseBoard, boards[currentMove]);
11575 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11576 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11577 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11578 reverseBoard[r][f] = piece;
11580 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11581 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11582 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11583 || (boards[currentMove][CASTLING][2] == NoRights ||
11584 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11585 && (boards[currentMove][CASTLING][5] == NoRights ||
11586 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11589 CopyBoard(flipBoard, soughtBoard);
11590 CopyBoard(rotateBoard, reverseBoard);
11591 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11592 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11593 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11596 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11597 if(appData.searchMode >= 5) {
11598 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11599 MakePieceList(soughtBoard, minSought);
11600 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11602 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11603 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11606 GameInfo dummyInfo;
11609 GameContainsPosition (FILE *f, ListGame *lg)
11611 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11612 int fromX, fromY, toX, toY;
11614 static int initDone=FALSE;
11616 // weed out games based on numerical tag comparison
11617 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11618 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11619 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11620 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11622 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11625 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11626 else CopyBoard(boards[scratch], initialPosition); // default start position
11629 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11630 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11633 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11634 fseek(f, lg->offset, 0);
11637 yyboardindex = scratch;
11638 quickFlag = plyNr+1;
11643 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11649 if(plyNr) return -1; // after we have seen moves, this is for new game
11652 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11653 case ImpossibleMove:
11654 case WhiteWins: // game ends here with these four
11657 case GameUnfinished:
11661 if(appData.testLegality) return -1;
11662 case WhiteCapturesEnPassant:
11663 case BlackCapturesEnPassant:
11664 case WhitePromotion:
11665 case BlackPromotion:
11666 case WhiteNonPromotion:
11667 case BlackNonPromotion:
11669 case WhiteKingSideCastle:
11670 case WhiteQueenSideCastle:
11671 case BlackKingSideCastle:
11672 case BlackQueenSideCastle:
11673 case WhiteKingSideCastleWild:
11674 case WhiteQueenSideCastleWild:
11675 case BlackKingSideCastleWild:
11676 case BlackQueenSideCastleWild:
11677 case WhiteHSideCastleFR:
11678 case WhiteASideCastleFR:
11679 case BlackHSideCastleFR:
11680 case BlackASideCastleFR:
11681 fromX = currentMoveString[0] - AAA;
11682 fromY = currentMoveString[1] - ONE;
11683 toX = currentMoveString[2] - AAA;
11684 toY = currentMoveString[3] - ONE;
11685 promoChar = currentMoveString[4];
11689 fromX = next == WhiteDrop ?
11690 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11691 (int) CharToPiece(ToLower(currentMoveString[0]));
11693 toX = currentMoveString[2] - AAA;
11694 toY = currentMoveString[3] - ONE;
11698 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11700 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11701 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11702 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11703 if(appData.findMirror) {
11704 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11705 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11710 /* Load the nth game from open file f */
11712 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11716 int gn = gameNumber;
11717 ListGame *lg = NULL;
11718 int numPGNTags = 0;
11720 GameMode oldGameMode;
11721 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11723 if (appData.debugMode)
11724 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11726 if (gameMode == Training )
11727 SetTrainingModeOff();
11729 oldGameMode = gameMode;
11730 if (gameMode != BeginningOfGame) {
11731 Reset(FALSE, TRUE);
11735 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11736 fclose(lastLoadGameFP);
11740 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11743 fseek(f, lg->offset, 0);
11744 GameListHighlight(gameNumber);
11745 pos = lg->position;
11749 DisplayError(_("Game number out of range"), 0);
11754 if (fseek(f, 0, 0) == -1) {
11755 if (f == lastLoadGameFP ?
11756 gameNumber == lastLoadGameNumber + 1 :
11760 DisplayError(_("Can't seek on game file"), 0);
11765 lastLoadGameFP = f;
11766 lastLoadGameNumber = gameNumber;
11767 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11768 lastLoadGameUseList = useList;
11772 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11773 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11774 lg->gameInfo.black);
11776 } else if (*title != NULLCHAR) {
11777 if (gameNumber > 1) {
11778 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11781 DisplayTitle(title);
11785 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11786 gameMode = PlayFromGameFile;
11790 currentMove = forwardMostMove = backwardMostMove = 0;
11791 CopyBoard(boards[0], initialPosition);
11795 * Skip the first gn-1 games in the file.
11796 * Also skip over anything that precedes an identifiable
11797 * start of game marker, to avoid being confused by
11798 * garbage at the start of the file. Currently
11799 * recognized start of game markers are the move number "1",
11800 * the pattern "gnuchess .* game", the pattern
11801 * "^[#;%] [^ ]* game file", and a PGN tag block.
11802 * A game that starts with one of the latter two patterns
11803 * will also have a move number 1, possibly
11804 * following a position diagram.
11805 * 5-4-02: Let's try being more lenient and allowing a game to
11806 * start with an unnumbered move. Does that break anything?
11808 cm = lastLoadGameStart = EndOfFile;
11810 yyboardindex = forwardMostMove;
11811 cm = (ChessMove) Myylex();
11814 if (cmailMsgLoaded) {
11815 nCmailGames = CMAIL_MAX_GAMES - gn;
11818 DisplayError(_("Game not found in file"), 0);
11825 lastLoadGameStart = cm;
11828 case MoveNumberOne:
11829 switch (lastLoadGameStart) {
11834 case MoveNumberOne:
11836 gn--; /* count this game */
11837 lastLoadGameStart = cm;
11846 switch (lastLoadGameStart) {
11849 case MoveNumberOne:
11851 gn--; /* count this game */
11852 lastLoadGameStart = cm;
11855 lastLoadGameStart = cm; /* game counted already */
11863 yyboardindex = forwardMostMove;
11864 cm = (ChessMove) Myylex();
11865 } while (cm == PGNTag || cm == Comment);
11872 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11873 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11874 != CMAIL_OLD_RESULT) {
11876 cmailResult[ CMAIL_MAX_GAMES
11877 - gn - 1] = CMAIL_OLD_RESULT;
11883 /* Only a NormalMove can be at the start of a game
11884 * without a position diagram. */
11885 if (lastLoadGameStart == EndOfFile ) {
11887 lastLoadGameStart = MoveNumberOne;
11896 if (appData.debugMode)
11897 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11899 if (cm == XBoardGame) {
11900 /* Skip any header junk before position diagram and/or move 1 */
11902 yyboardindex = forwardMostMove;
11903 cm = (ChessMove) Myylex();
11905 if (cm == EndOfFile ||
11906 cm == GNUChessGame || cm == XBoardGame) {
11907 /* Empty game; pretend end-of-file and handle later */
11912 if (cm == MoveNumberOne || cm == PositionDiagram ||
11913 cm == PGNTag || cm == Comment)
11916 } else if (cm == GNUChessGame) {
11917 if (gameInfo.event != NULL) {
11918 free(gameInfo.event);
11920 gameInfo.event = StrSave(yy_text);
11923 startedFromSetupPosition = FALSE;
11924 while (cm == PGNTag) {
11925 if (appData.debugMode)
11926 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11927 err = ParsePGNTag(yy_text, &gameInfo);
11928 if (!err) numPGNTags++;
11930 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11931 if(gameInfo.variant != oldVariant) {
11932 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11933 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11934 InitPosition(TRUE);
11935 oldVariant = gameInfo.variant;
11936 if (appData.debugMode)
11937 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11941 if (gameInfo.fen != NULL) {
11942 Board initial_position;
11943 startedFromSetupPosition = TRUE;
11944 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11946 DisplayError(_("Bad FEN position in file"), 0);
11949 CopyBoard(boards[0], initial_position);
11950 if (blackPlaysFirst) {
11951 currentMove = forwardMostMove = backwardMostMove = 1;
11952 CopyBoard(boards[1], initial_position);
11953 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11954 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11955 timeRemaining[0][1] = whiteTimeRemaining;
11956 timeRemaining[1][1] = blackTimeRemaining;
11957 if (commentList[0] != NULL) {
11958 commentList[1] = commentList[0];
11959 commentList[0] = NULL;
11962 currentMove = forwardMostMove = backwardMostMove = 0;
11964 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11966 initialRulePlies = FENrulePlies;
11967 for( i=0; i< nrCastlingRights; i++ )
11968 initialRights[i] = initial_position[CASTLING][i];
11970 yyboardindex = forwardMostMove;
11971 free(gameInfo.fen);
11972 gameInfo.fen = NULL;
11975 yyboardindex = forwardMostMove;
11976 cm = (ChessMove) Myylex();
11978 /* Handle comments interspersed among the tags */
11979 while (cm == Comment) {
11981 if (appData.debugMode)
11982 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11984 AppendComment(currentMove, p, FALSE);
11985 yyboardindex = forwardMostMove;
11986 cm = (ChessMove) Myylex();
11990 /* don't rely on existence of Event tag since if game was
11991 * pasted from clipboard the Event tag may not exist
11993 if (numPGNTags > 0){
11995 if (gameInfo.variant == VariantNormal) {
11996 VariantClass v = StringToVariant(gameInfo.event);
11997 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11998 if(v < VariantShogi) gameInfo.variant = v;
12001 if( appData.autoDisplayTags ) {
12002 tags = PGNTags(&gameInfo);
12003 TagsPopUp(tags, CmailMsg());
12008 /* Make something up, but don't display it now */
12013 if (cm == PositionDiagram) {
12016 Board initial_position;
12018 if (appData.debugMode)
12019 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12021 if (!startedFromSetupPosition) {
12023 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12024 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12035 initial_position[i][j++] = CharToPiece(*p);
12038 while (*p == ' ' || *p == '\t' ||
12039 *p == '\n' || *p == '\r') p++;
12041 if (strncmp(p, "black", strlen("black"))==0)
12042 blackPlaysFirst = TRUE;
12044 blackPlaysFirst = FALSE;
12045 startedFromSetupPosition = TRUE;
12047 CopyBoard(boards[0], initial_position);
12048 if (blackPlaysFirst) {
12049 currentMove = forwardMostMove = backwardMostMove = 1;
12050 CopyBoard(boards[1], initial_position);
12051 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12052 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12053 timeRemaining[0][1] = whiteTimeRemaining;
12054 timeRemaining[1][1] = blackTimeRemaining;
12055 if (commentList[0] != NULL) {
12056 commentList[1] = commentList[0];
12057 commentList[0] = NULL;
12060 currentMove = forwardMostMove = backwardMostMove = 0;
12063 yyboardindex = forwardMostMove;
12064 cm = (ChessMove) Myylex();
12067 if (first.pr == NoProc) {
12068 StartChessProgram(&first);
12070 InitChessProgram(&first, FALSE);
12071 SendToProgram("force\n", &first);
12072 if (startedFromSetupPosition) {
12073 SendBoard(&first, forwardMostMove);
12074 if (appData.debugMode) {
12075 fprintf(debugFP, "Load Game\n");
12077 DisplayBothClocks();
12080 /* [HGM] server: flag to write setup moves in broadcast file as one */
12081 loadFlag = appData.suppressLoadMoves;
12083 while (cm == Comment) {
12085 if (appData.debugMode)
12086 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12088 AppendComment(currentMove, p, FALSE);
12089 yyboardindex = forwardMostMove;
12090 cm = (ChessMove) Myylex();
12093 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12094 cm == WhiteWins || cm == BlackWins ||
12095 cm == GameIsDrawn || cm == GameUnfinished) {
12096 DisplayMessage("", _("No moves in game"));
12097 if (cmailMsgLoaded) {
12098 if (appData.debugMode)
12099 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12103 DrawPosition(FALSE, boards[currentMove]);
12104 DisplayBothClocks();
12105 gameMode = EditGame;
12112 // [HGM] PV info: routine tests if comment empty
12113 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12114 DisplayComment(currentMove - 1, commentList[currentMove]);
12116 if (!matchMode && appData.timeDelay != 0)
12117 DrawPosition(FALSE, boards[currentMove]);
12119 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12120 programStats.ok_to_send = 1;
12123 /* if the first token after the PGN tags is a move
12124 * and not move number 1, retrieve it from the parser
12126 if (cm != MoveNumberOne)
12127 LoadGameOneMove(cm);
12129 /* load the remaining moves from the file */
12130 while (LoadGameOneMove(EndOfFile)) {
12131 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12132 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12135 /* rewind to the start of the game */
12136 currentMove = backwardMostMove;
12138 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12140 if (oldGameMode == AnalyzeFile ||
12141 oldGameMode == AnalyzeMode) {
12142 AnalyzeFileEvent();
12145 if (!matchMode && pos >= 0) {
12146 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12148 if (matchMode || appData.timeDelay == 0) {
12150 } else if (appData.timeDelay > 0) {
12151 AutoPlayGameLoop();
12154 if (appData.debugMode)
12155 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12157 loadFlag = 0; /* [HGM] true game starts */
12161 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12163 ReloadPosition (int offset)
12165 int positionNumber = lastLoadPositionNumber + offset;
12166 if (lastLoadPositionFP == NULL) {
12167 DisplayError(_("No position has been loaded yet"), 0);
12170 if (positionNumber <= 0) {
12171 DisplayError(_("Can't back up any further"), 0);
12174 return LoadPosition(lastLoadPositionFP, positionNumber,
12175 lastLoadPositionTitle);
12178 /* Load the nth position from the given file */
12180 LoadPositionFromFile (char *filename, int n, char *title)
12185 if (strcmp(filename, "-") == 0) {
12186 return LoadPosition(stdin, n, "stdin");
12188 f = fopen(filename, "rb");
12190 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12191 DisplayError(buf, errno);
12194 return LoadPosition(f, n, title);
12199 /* Load the nth position from the given open file, and close it */
12201 LoadPosition (FILE *f, int positionNumber, char *title)
12203 char *p, line[MSG_SIZ];
12204 Board initial_position;
12205 int i, j, fenMode, pn;
12207 if (gameMode == Training )
12208 SetTrainingModeOff();
12210 if (gameMode != BeginningOfGame) {
12211 Reset(FALSE, TRUE);
12213 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12214 fclose(lastLoadPositionFP);
12216 if (positionNumber == 0) positionNumber = 1;
12217 lastLoadPositionFP = f;
12218 lastLoadPositionNumber = positionNumber;
12219 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12220 if (first.pr == NoProc && !appData.noChessProgram) {
12221 StartChessProgram(&first);
12222 InitChessProgram(&first, FALSE);
12224 pn = positionNumber;
12225 if (positionNumber < 0) {
12226 /* Negative position number means to seek to that byte offset */
12227 if (fseek(f, -positionNumber, 0) == -1) {
12228 DisplayError(_("Can't seek on position file"), 0);
12233 if (fseek(f, 0, 0) == -1) {
12234 if (f == lastLoadPositionFP ?
12235 positionNumber == lastLoadPositionNumber + 1 :
12236 positionNumber == 1) {
12239 DisplayError(_("Can't seek on position file"), 0);
12244 /* See if this file is FEN or old-style xboard */
12245 if (fgets(line, MSG_SIZ, f) == NULL) {
12246 DisplayError(_("Position not found in file"), 0);
12249 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12250 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12253 if (fenMode || line[0] == '#') pn--;
12255 /* skip positions before number pn */
12256 if (fgets(line, MSG_SIZ, f) == NULL) {
12258 DisplayError(_("Position not found in file"), 0);
12261 if (fenMode || line[0] == '#') pn--;
12266 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12267 DisplayError(_("Bad FEN position in file"), 0);
12271 (void) fgets(line, MSG_SIZ, f);
12272 (void) fgets(line, MSG_SIZ, f);
12274 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12275 (void) fgets(line, MSG_SIZ, f);
12276 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12279 initial_position[i][j++] = CharToPiece(*p);
12283 blackPlaysFirst = FALSE;
12285 (void) fgets(line, MSG_SIZ, f);
12286 if (strncmp(line, "black", strlen("black"))==0)
12287 blackPlaysFirst = TRUE;
12290 startedFromSetupPosition = TRUE;
12292 CopyBoard(boards[0], initial_position);
12293 if (blackPlaysFirst) {
12294 currentMove = forwardMostMove = backwardMostMove = 1;
12295 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12296 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12297 CopyBoard(boards[1], initial_position);
12298 DisplayMessage("", _("Black to play"));
12300 currentMove = forwardMostMove = backwardMostMove = 0;
12301 DisplayMessage("", _("White to play"));
12303 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12304 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12305 SendToProgram("force\n", &first);
12306 SendBoard(&first, forwardMostMove);
12308 if (appData.debugMode) {
12310 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12311 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12312 fprintf(debugFP, "Load Position\n");
12315 if (positionNumber > 1) {
12316 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12317 DisplayTitle(line);
12319 DisplayTitle(title);
12321 gameMode = EditGame;
12324 timeRemaining[0][1] = whiteTimeRemaining;
12325 timeRemaining[1][1] = blackTimeRemaining;
12326 DrawPosition(FALSE, boards[currentMove]);
12333 CopyPlayerNameIntoFileName (char **dest, char *src)
12335 while (*src != NULLCHAR && *src != ',') {
12340 *(*dest)++ = *src++;
12346 DefaultFileName (char *ext)
12348 static char def[MSG_SIZ];
12351 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12353 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12355 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12357 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12364 /* Save the current game to the given file */
12366 SaveGameToFile (char *filename, int append)
12370 int result, i, t,tot=0;
12372 if (strcmp(filename, "-") == 0) {
12373 return SaveGame(stdout, 0, NULL);
12375 for(i=0; i<10; i++) { // upto 10 tries
12376 f = fopen(filename, append ? "a" : "w");
12377 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12378 if(f || errno != 13) break;
12379 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12383 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12384 DisplayError(buf, errno);
12387 safeStrCpy(buf, lastMsg, MSG_SIZ);
12388 DisplayMessage(_("Waiting for access to save file"), "");
12389 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12390 DisplayMessage(_("Saving game"), "");
12391 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12392 result = SaveGame(f, 0, NULL);
12393 DisplayMessage(buf, "");
12400 SavePart (char *str)
12402 static char buf[MSG_SIZ];
12405 p = strchr(str, ' ');
12406 if (p == NULL) return str;
12407 strncpy(buf, str, p - str);
12408 buf[p - str] = NULLCHAR;
12412 #define PGN_MAX_LINE 75
12414 #define PGN_SIDE_WHITE 0
12415 #define PGN_SIDE_BLACK 1
12418 FindFirstMoveOutOfBook (int side)
12422 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12423 int index = backwardMostMove;
12424 int has_book_hit = 0;
12426 if( (index % 2) != side ) {
12430 while( index < forwardMostMove ) {
12431 /* Check to see if engine is in book */
12432 int depth = pvInfoList[index].depth;
12433 int score = pvInfoList[index].score;
12439 else if( score == 0 && depth == 63 ) {
12440 in_book = 1; /* Zappa */
12442 else if( score == 2 && depth == 99 ) {
12443 in_book = 1; /* Abrok */
12446 has_book_hit += in_book;
12462 GetOutOfBookInfo (char * buf)
12466 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12468 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12469 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12473 if( oob[0] >= 0 || oob[1] >= 0 ) {
12474 for( i=0; i<2; i++ ) {
12478 if( i > 0 && oob[0] >= 0 ) {
12479 strcat( buf, " " );
12482 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12483 sprintf( buf+strlen(buf), "%s%.2f",
12484 pvInfoList[idx].score >= 0 ? "+" : "",
12485 pvInfoList[idx].score / 100.0 );
12491 /* Save game in PGN style and close the file */
12493 SaveGamePGN (FILE *f)
12495 int i, offset, linelen, newblock;
12499 int movelen, numlen, blank;
12500 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12502 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12504 tm = time((time_t *) NULL);
12506 PrintPGNTags(f, &gameInfo);
12508 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12510 if (backwardMostMove > 0 || startedFromSetupPosition) {
12511 char *fen = PositionToFEN(backwardMostMove, NULL);
12512 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12513 fprintf(f, "\n{--------------\n");
12514 PrintPosition(f, backwardMostMove);
12515 fprintf(f, "--------------}\n");
12519 /* [AS] Out of book annotation */
12520 if( appData.saveOutOfBookInfo ) {
12523 GetOutOfBookInfo( buf );
12525 if( buf[0] != '\0' ) {
12526 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12533 i = backwardMostMove;
12537 while (i < forwardMostMove) {
12538 /* Print comments preceding this move */
12539 if (commentList[i] != NULL) {
12540 if (linelen > 0) fprintf(f, "\n");
12541 fprintf(f, "%s", commentList[i]);
12546 /* Format move number */
12548 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12551 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12553 numtext[0] = NULLCHAR;
12555 numlen = strlen(numtext);
12558 /* Print move number */
12559 blank = linelen > 0 && numlen > 0;
12560 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12569 fprintf(f, "%s", numtext);
12573 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12574 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12577 blank = linelen > 0 && movelen > 0;
12578 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12587 fprintf(f, "%s", move_buffer);
12588 linelen += movelen;
12590 /* [AS] Add PV info if present */
12591 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12592 /* [HGM] add time */
12593 char buf[MSG_SIZ]; int seconds;
12595 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12601 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12604 seconds = (seconds + 4)/10; // round to full seconds
12606 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12608 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12611 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12612 pvInfoList[i].score >= 0 ? "+" : "",
12613 pvInfoList[i].score / 100.0,
12614 pvInfoList[i].depth,
12617 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12619 /* Print score/depth */
12620 blank = linelen > 0 && movelen > 0;
12621 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12630 fprintf(f, "%s", move_buffer);
12631 linelen += movelen;
12637 /* Start a new line */
12638 if (linelen > 0) fprintf(f, "\n");
12640 /* Print comments after last move */
12641 if (commentList[i] != NULL) {
12642 fprintf(f, "%s\n", commentList[i]);
12646 if (gameInfo.resultDetails != NULL &&
12647 gameInfo.resultDetails[0] != NULLCHAR) {
12648 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12649 PGNResult(gameInfo.result));
12651 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12655 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12659 /* Save game in old style and close the file */
12661 SaveGameOldStyle (FILE *f)
12666 tm = time((time_t *) NULL);
12668 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12671 if (backwardMostMove > 0 || startedFromSetupPosition) {
12672 fprintf(f, "\n[--------------\n");
12673 PrintPosition(f, backwardMostMove);
12674 fprintf(f, "--------------]\n");
12679 i = backwardMostMove;
12680 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12682 while (i < forwardMostMove) {
12683 if (commentList[i] != NULL) {
12684 fprintf(f, "[%s]\n", commentList[i]);
12687 if ((i % 2) == 1) {
12688 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12691 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12693 if (commentList[i] != NULL) {
12697 if (i >= forwardMostMove) {
12701 fprintf(f, "%s\n", parseList[i]);
12706 if (commentList[i] != NULL) {
12707 fprintf(f, "[%s]\n", commentList[i]);
12710 /* This isn't really the old style, but it's close enough */
12711 if (gameInfo.resultDetails != NULL &&
12712 gameInfo.resultDetails[0] != NULLCHAR) {
12713 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12714 gameInfo.resultDetails);
12716 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12723 /* Save the current game to open file f and close the file */
12725 SaveGame (FILE *f, int dummy, char *dummy2)
12727 if (gameMode == EditPosition) EditPositionDone(TRUE);
12728 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12729 if (appData.oldSaveStyle)
12730 return SaveGameOldStyle(f);
12732 return SaveGamePGN(f);
12735 /* Save the current position to the given file */
12737 SavePositionToFile (char *filename)
12742 if (strcmp(filename, "-") == 0) {
12743 return SavePosition(stdout, 0, NULL);
12745 f = fopen(filename, "a");
12747 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12748 DisplayError(buf, errno);
12751 safeStrCpy(buf, lastMsg, MSG_SIZ);
12752 DisplayMessage(_("Waiting for access to save file"), "");
12753 flock(fileno(f), LOCK_EX); // [HGM] lock
12754 DisplayMessage(_("Saving position"), "");
12755 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12756 SavePosition(f, 0, NULL);
12757 DisplayMessage(buf, "");
12763 /* Save the current position to the given open file and close the file */
12765 SavePosition (FILE *f, int dummy, char *dummy2)
12770 if (gameMode == EditPosition) EditPositionDone(TRUE);
12771 if (appData.oldSaveStyle) {
12772 tm = time((time_t *) NULL);
12774 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12776 fprintf(f, "[--------------\n");
12777 PrintPosition(f, currentMove);
12778 fprintf(f, "--------------]\n");
12780 fen = PositionToFEN(currentMove, NULL);
12781 fprintf(f, "%s\n", fen);
12789 ReloadCmailMsgEvent (int unregister)
12792 static char *inFilename = NULL;
12793 static char *outFilename;
12795 struct stat inbuf, outbuf;
12798 /* Any registered moves are unregistered if unregister is set, */
12799 /* i.e. invoked by the signal handler */
12801 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12802 cmailMoveRegistered[i] = FALSE;
12803 if (cmailCommentList[i] != NULL) {
12804 free(cmailCommentList[i]);
12805 cmailCommentList[i] = NULL;
12808 nCmailMovesRegistered = 0;
12811 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12812 cmailResult[i] = CMAIL_NOT_RESULT;
12816 if (inFilename == NULL) {
12817 /* Because the filenames are static they only get malloced once */
12818 /* and they never get freed */
12819 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12820 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12822 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12823 sprintf(outFilename, "%s.out", appData.cmailGameName);
12826 status = stat(outFilename, &outbuf);
12828 cmailMailedMove = FALSE;
12830 status = stat(inFilename, &inbuf);
12831 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12834 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12835 counts the games, notes how each one terminated, etc.
12837 It would be nice to remove this kludge and instead gather all
12838 the information while building the game list. (And to keep it
12839 in the game list nodes instead of having a bunch of fixed-size
12840 parallel arrays.) Note this will require getting each game's
12841 termination from the PGN tags, as the game list builder does
12842 not process the game moves. --mann
12844 cmailMsgLoaded = TRUE;
12845 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12847 /* Load first game in the file or popup game menu */
12848 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12850 #endif /* !WIN32 */
12858 char string[MSG_SIZ];
12860 if ( cmailMailedMove
12861 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12862 return TRUE; /* Allow free viewing */
12865 /* Unregister move to ensure that we don't leave RegisterMove */
12866 /* with the move registered when the conditions for registering no */
12868 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12869 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12870 nCmailMovesRegistered --;
12872 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12874 free(cmailCommentList[lastLoadGameNumber - 1]);
12875 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12879 if (cmailOldMove == -1) {
12880 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12884 if (currentMove > cmailOldMove + 1) {
12885 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12889 if (currentMove < cmailOldMove) {
12890 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12894 if (forwardMostMove > currentMove) {
12895 /* Silently truncate extra moves */
12899 if ( (currentMove == cmailOldMove + 1)
12900 || ( (currentMove == cmailOldMove)
12901 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12902 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12903 if (gameInfo.result != GameUnfinished) {
12904 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12907 if (commentList[currentMove] != NULL) {
12908 cmailCommentList[lastLoadGameNumber - 1]
12909 = StrSave(commentList[currentMove]);
12911 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12913 if (appData.debugMode)
12914 fprintf(debugFP, "Saving %s for game %d\n",
12915 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12917 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12919 f = fopen(string, "w");
12920 if (appData.oldSaveStyle) {
12921 SaveGameOldStyle(f); /* also closes the file */
12923 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12924 f = fopen(string, "w");
12925 SavePosition(f, 0, NULL); /* also closes the file */
12927 fprintf(f, "{--------------\n");
12928 PrintPosition(f, currentMove);
12929 fprintf(f, "--------------}\n\n");
12931 SaveGame(f, 0, NULL); /* also closes the file*/
12934 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12935 nCmailMovesRegistered ++;
12936 } else if (nCmailGames == 1) {
12937 DisplayError(_("You have not made a move yet"), 0);
12948 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12949 FILE *commandOutput;
12950 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12951 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12957 if (! cmailMsgLoaded) {
12958 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12962 if (nCmailGames == nCmailResults) {
12963 DisplayError(_("No unfinished games"), 0);
12967 #if CMAIL_PROHIBIT_REMAIL
12968 if (cmailMailedMove) {
12969 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);
12970 DisplayError(msg, 0);
12975 if (! (cmailMailedMove || RegisterMove())) return;
12977 if ( cmailMailedMove
12978 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12979 snprintf(string, MSG_SIZ, partCommandString,
12980 appData.debugMode ? " -v" : "", appData.cmailGameName);
12981 commandOutput = popen(string, "r");
12983 if (commandOutput == NULL) {
12984 DisplayError(_("Failed to invoke cmail"), 0);
12986 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12987 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12989 if (nBuffers > 1) {
12990 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12991 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12992 nBytes = MSG_SIZ - 1;
12994 (void) memcpy(msg, buffer, nBytes);
12996 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12998 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12999 cmailMailedMove = TRUE; /* Prevent >1 moves */
13002 for (i = 0; i < nCmailGames; i ++) {
13003 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13008 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13010 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13012 appData.cmailGameName,
13014 LoadGameFromFile(buffer, 1, buffer, FALSE);
13015 cmailMsgLoaded = FALSE;
13019 DisplayInformation(msg);
13020 pclose(commandOutput);
13023 if ((*cmailMsg) != '\0') {
13024 DisplayInformation(cmailMsg);
13029 #endif /* !WIN32 */
13038 int prependComma = 0;
13040 char string[MSG_SIZ]; /* Space for game-list */
13043 if (!cmailMsgLoaded) return "";
13045 if (cmailMailedMove) {
13046 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13048 /* Create a list of games left */
13049 snprintf(string, MSG_SIZ, "[");
13050 for (i = 0; i < nCmailGames; i ++) {
13051 if (! ( cmailMoveRegistered[i]
13052 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13053 if (prependComma) {
13054 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13056 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13060 strcat(string, number);
13063 strcat(string, "]");
13065 if (nCmailMovesRegistered + nCmailResults == 0) {
13066 switch (nCmailGames) {
13068 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13072 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13076 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13081 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13083 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13088 if (nCmailResults == nCmailGames) {
13089 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13091 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13096 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13108 if (gameMode == Training)
13109 SetTrainingModeOff();
13112 cmailMsgLoaded = FALSE;
13113 if (appData.icsActive) {
13114 SendToICS(ics_prefix);
13115 SendToICS("refresh\n");
13120 ExitEvent (int status)
13124 /* Give up on clean exit */
13128 /* Keep trying for clean exit */
13132 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13134 if (telnetISR != NULL) {
13135 RemoveInputSource(telnetISR);
13137 if (icsPR != NoProc) {
13138 DestroyChildProcess(icsPR, TRUE);
13141 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13142 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13144 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13145 /* make sure this other one finishes before killing it! */
13146 if(endingGame) { int count = 0;
13147 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13148 while(endingGame && count++ < 10) DoSleep(1);
13149 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13152 /* Kill off chess programs */
13153 if (first.pr != NoProc) {
13156 DoSleep( appData.delayBeforeQuit );
13157 SendToProgram("quit\n", &first);
13158 DoSleep( appData.delayAfterQuit );
13159 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13161 if (second.pr != NoProc) {
13162 DoSleep( appData.delayBeforeQuit );
13163 SendToProgram("quit\n", &second);
13164 DoSleep( appData.delayAfterQuit );
13165 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13167 if (first.isr != NULL) {
13168 RemoveInputSource(first.isr);
13170 if (second.isr != NULL) {
13171 RemoveInputSource(second.isr);
13174 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13175 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13177 ShutDownFrontEnd();
13184 if (appData.debugMode)
13185 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13189 if (gameMode == MachinePlaysWhite ||
13190 gameMode == MachinePlaysBlack) {
13193 DisplayBothClocks();
13195 if (gameMode == PlayFromGameFile) {
13196 if (appData.timeDelay >= 0)
13197 AutoPlayGameLoop();
13198 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13199 Reset(FALSE, TRUE);
13200 SendToICS(ics_prefix);
13201 SendToICS("refresh\n");
13202 } else if (currentMove < forwardMostMove) {
13203 ForwardInner(forwardMostMove);
13205 pauseExamInvalid = FALSE;
13207 switch (gameMode) {
13211 pauseExamForwardMostMove = forwardMostMove;
13212 pauseExamInvalid = FALSE;
13215 case IcsPlayingWhite:
13216 case IcsPlayingBlack:
13220 case PlayFromGameFile:
13221 (void) StopLoadGameTimer();
13225 case BeginningOfGame:
13226 if (appData.icsActive) return;
13227 /* else fall through */
13228 case MachinePlaysWhite:
13229 case MachinePlaysBlack:
13230 case TwoMachinesPlay:
13231 if (forwardMostMove == 0)
13232 return; /* don't pause if no one has moved */
13233 if ((gameMode == MachinePlaysWhite &&
13234 !WhiteOnMove(forwardMostMove)) ||
13235 (gameMode == MachinePlaysBlack &&
13236 WhiteOnMove(forwardMostMove))) {
13247 EditCommentEvent ()
13249 char title[MSG_SIZ];
13251 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13252 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13254 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13255 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13256 parseList[currentMove - 1]);
13259 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13266 char *tags = PGNTags(&gameInfo);
13268 EditTagsPopUp(tags, NULL);
13273 AnalyzeModeEvent ()
13275 if (appData.noChessProgram || gameMode == AnalyzeMode)
13278 if (gameMode != AnalyzeFile) {
13279 if (!appData.icsEngineAnalyze) {
13281 if (gameMode != EditGame) return;
13283 ResurrectChessProgram();
13284 SendToProgram("analyze\n", &first);
13285 first.analyzing = TRUE;
13286 /*first.maybeThinking = TRUE;*/
13287 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13288 EngineOutputPopUp();
13290 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13295 StartAnalysisClock();
13296 GetTimeMark(&lastNodeCountTime);
13301 AnalyzeFileEvent ()
13303 if (appData.noChessProgram || gameMode == AnalyzeFile)
13306 if (gameMode != AnalyzeMode) {
13308 if (gameMode != EditGame) return;
13309 ResurrectChessProgram();
13310 SendToProgram("analyze\n", &first);
13311 first.analyzing = TRUE;
13312 /*first.maybeThinking = TRUE;*/
13313 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13314 EngineOutputPopUp();
13316 gameMode = AnalyzeFile;
13321 StartAnalysisClock();
13322 GetTimeMark(&lastNodeCountTime);
13324 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13328 MachineWhiteEvent ()
13331 char *bookHit = NULL;
13333 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13337 if (gameMode == PlayFromGameFile ||
13338 gameMode == TwoMachinesPlay ||
13339 gameMode == Training ||
13340 gameMode == AnalyzeMode ||
13341 gameMode == EndOfGame)
13344 if (gameMode == EditPosition)
13345 EditPositionDone(TRUE);
13347 if (!WhiteOnMove(currentMove)) {
13348 DisplayError(_("It is not White's turn"), 0);
13352 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13355 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13356 gameMode == AnalyzeFile)
13359 ResurrectChessProgram(); /* in case it isn't running */
13360 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13361 gameMode = MachinePlaysWhite;
13364 gameMode = MachinePlaysWhite;
13368 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13370 if (first.sendName) {
13371 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13372 SendToProgram(buf, &first);
13374 if (first.sendTime) {
13375 if (first.useColors) {
13376 SendToProgram("black\n", &first); /*gnu kludge*/
13378 SendTimeRemaining(&first, TRUE);
13380 if (first.useColors) {
13381 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13383 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13384 SetMachineThinkingEnables();
13385 first.maybeThinking = TRUE;
13389 if (appData.autoFlipView && !flipView) {
13390 flipView = !flipView;
13391 DrawPosition(FALSE, NULL);
13392 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13395 if(bookHit) { // [HGM] book: simulate book reply
13396 static char bookMove[MSG_SIZ]; // a bit generous?
13398 programStats.nodes = programStats.depth = programStats.time =
13399 programStats.score = programStats.got_only_move = 0;
13400 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13402 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13403 strcat(bookMove, bookHit);
13404 HandleMachineMove(bookMove, &first);
13409 MachineBlackEvent ()
13412 char *bookHit = NULL;
13414 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13418 if (gameMode == PlayFromGameFile ||
13419 gameMode == TwoMachinesPlay ||
13420 gameMode == Training ||
13421 gameMode == AnalyzeMode ||
13422 gameMode == EndOfGame)
13425 if (gameMode == EditPosition)
13426 EditPositionDone(TRUE);
13428 if (WhiteOnMove(currentMove)) {
13429 DisplayError(_("It is not Black's turn"), 0);
13433 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13436 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13437 gameMode == AnalyzeFile)
13440 ResurrectChessProgram(); /* in case it isn't running */
13441 gameMode = MachinePlaysBlack;
13445 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13447 if (first.sendName) {
13448 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13449 SendToProgram(buf, &first);
13451 if (first.sendTime) {
13452 if (first.useColors) {
13453 SendToProgram("white\n", &first); /*gnu kludge*/
13455 SendTimeRemaining(&first, FALSE);
13457 if (first.useColors) {
13458 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13460 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13461 SetMachineThinkingEnables();
13462 first.maybeThinking = TRUE;
13465 if (appData.autoFlipView && flipView) {
13466 flipView = !flipView;
13467 DrawPosition(FALSE, NULL);
13468 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13470 if(bookHit) { // [HGM] book: simulate book reply
13471 static char bookMove[MSG_SIZ]; // a bit generous?
13473 programStats.nodes = programStats.depth = programStats.time =
13474 programStats.score = programStats.got_only_move = 0;
13475 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13477 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13478 strcat(bookMove, bookHit);
13479 HandleMachineMove(bookMove, &first);
13485 DisplayTwoMachinesTitle ()
13488 if (appData.matchGames > 0) {
13489 if(appData.tourneyFile[0]) {
13490 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13491 gameInfo.white, _("vs."), gameInfo.black,
13492 nextGame+1, appData.matchGames+1,
13493 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13495 if (first.twoMachinesColor[0] == 'w') {
13496 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13497 gameInfo.white, _("vs."), gameInfo.black,
13498 first.matchWins, second.matchWins,
13499 matchGame - 1 - (first.matchWins + second.matchWins));
13501 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13502 gameInfo.white, _("vs."), gameInfo.black,
13503 second.matchWins, first.matchWins,
13504 matchGame - 1 - (first.matchWins + second.matchWins));
13507 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13513 SettingsMenuIfReady ()
13515 if (second.lastPing != second.lastPong) {
13516 DisplayMessage("", _("Waiting for second chess program"));
13517 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13521 DisplayMessage("", "");
13522 SettingsPopUp(&second);
13526 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13529 if (cps->pr == NoProc) {
13530 StartChessProgram(cps);
13531 if (cps->protocolVersion == 1) {
13534 /* kludge: allow timeout for initial "feature" command */
13536 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13537 DisplayMessage("", buf);
13538 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13546 TwoMachinesEvent P((void))
13550 ChessProgramState *onmove;
13551 char *bookHit = NULL;
13552 static int stalling = 0;
13556 if (appData.noChessProgram) return;
13558 switch (gameMode) {
13559 case TwoMachinesPlay:
13561 case MachinePlaysWhite:
13562 case MachinePlaysBlack:
13563 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13564 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13568 case BeginningOfGame:
13569 case PlayFromGameFile:
13572 if (gameMode != EditGame) return;
13575 EditPositionDone(TRUE);
13586 // forwardMostMove = currentMove;
13587 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13589 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13591 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13592 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13593 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13597 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13598 SendToProgram("force\n", &second);
13600 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13603 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13604 if(appData.matchPause>10000 || appData.matchPause<10)
13605 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13606 wait = SubtractTimeMarks(&now, &pauseStart);
13607 if(wait < appData.matchPause) {
13608 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13611 // we are now committed to starting the game
13613 DisplayMessage("", "");
13614 if (startedFromSetupPosition) {
13615 SendBoard(&second, backwardMostMove);
13616 if (appData.debugMode) {
13617 fprintf(debugFP, "Two Machines\n");
13620 for (i = backwardMostMove; i < forwardMostMove; i++) {
13621 SendMoveToProgram(i, &second);
13624 gameMode = TwoMachinesPlay;
13626 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13628 DisplayTwoMachinesTitle();
13630 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13635 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13636 SendToProgram(first.computerString, &first);
13637 if (first.sendName) {
13638 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13639 SendToProgram(buf, &first);
13641 SendToProgram(second.computerString, &second);
13642 if (second.sendName) {
13643 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13644 SendToProgram(buf, &second);
13648 if (!first.sendTime || !second.sendTime) {
13649 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13650 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13652 if (onmove->sendTime) {
13653 if (onmove->useColors) {
13654 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13656 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13658 if (onmove->useColors) {
13659 SendToProgram(onmove->twoMachinesColor, onmove);
13661 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13662 // SendToProgram("go\n", onmove);
13663 onmove->maybeThinking = TRUE;
13664 SetMachineThinkingEnables();
13668 if(bookHit) { // [HGM] book: simulate book reply
13669 static char bookMove[MSG_SIZ]; // a bit generous?
13671 programStats.nodes = programStats.depth = programStats.time =
13672 programStats.score = programStats.got_only_move = 0;
13673 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13675 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13676 strcat(bookMove, bookHit);
13677 savedMessage = bookMove; // args for deferred call
13678 savedState = onmove;
13679 ScheduleDelayedEvent(DeferredBookMove, 1);
13686 if (gameMode == Training) {
13687 SetTrainingModeOff();
13688 gameMode = PlayFromGameFile;
13689 DisplayMessage("", _("Training mode off"));
13691 gameMode = Training;
13692 animateTraining = appData.animate;
13694 /* make sure we are not already at the end of the game */
13695 if (currentMove < forwardMostMove) {
13696 SetTrainingModeOn();
13697 DisplayMessage("", _("Training mode on"));
13699 gameMode = PlayFromGameFile;
13700 DisplayError(_("Already at end of game"), 0);
13709 if (!appData.icsActive) return;
13710 switch (gameMode) {
13711 case IcsPlayingWhite:
13712 case IcsPlayingBlack:
13715 case BeginningOfGame:
13723 EditPositionDone(TRUE);
13736 gameMode = IcsIdle;
13746 switch (gameMode) {
13748 SetTrainingModeOff();
13750 case MachinePlaysWhite:
13751 case MachinePlaysBlack:
13752 case BeginningOfGame:
13753 SendToProgram("force\n", &first);
13754 SetUserThinkingEnables();
13756 case PlayFromGameFile:
13757 (void) StopLoadGameTimer();
13758 if (gameFileFP != NULL) {
13763 EditPositionDone(TRUE);
13768 SendToProgram("force\n", &first);
13770 case TwoMachinesPlay:
13771 GameEnds(EndOfFile, NULL, GE_PLAYER);
13772 ResurrectChessProgram();
13773 SetUserThinkingEnables();
13776 ResurrectChessProgram();
13778 case IcsPlayingBlack:
13779 case IcsPlayingWhite:
13780 DisplayError(_("Warning: You are still playing a game"), 0);
13783 DisplayError(_("Warning: You are still observing a game"), 0);
13786 DisplayError(_("Warning: You are still examining a game"), 0);
13797 first.offeredDraw = second.offeredDraw = 0;
13799 if (gameMode == PlayFromGameFile) {
13800 whiteTimeRemaining = timeRemaining[0][currentMove];
13801 blackTimeRemaining = timeRemaining[1][currentMove];
13805 if (gameMode == MachinePlaysWhite ||
13806 gameMode == MachinePlaysBlack ||
13807 gameMode == TwoMachinesPlay ||
13808 gameMode == EndOfGame) {
13809 i = forwardMostMove;
13810 while (i > currentMove) {
13811 SendToProgram("undo\n", &first);
13814 if(!adjustedClock) {
13815 whiteTimeRemaining = timeRemaining[0][currentMove];
13816 blackTimeRemaining = timeRemaining[1][currentMove];
13817 DisplayBothClocks();
13819 if (whiteFlag || blackFlag) {
13820 whiteFlag = blackFlag = 0;
13825 gameMode = EditGame;
13832 EditPositionEvent ()
13834 if (gameMode == EditPosition) {
13840 if (gameMode != EditGame) return;
13842 gameMode = EditPosition;
13845 if (currentMove > 0)
13846 CopyBoard(boards[0], boards[currentMove]);
13848 blackPlaysFirst = !WhiteOnMove(currentMove);
13850 currentMove = forwardMostMove = backwardMostMove = 0;
13851 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13853 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13859 /* [DM] icsEngineAnalyze - possible call from other functions */
13860 if (appData.icsEngineAnalyze) {
13861 appData.icsEngineAnalyze = FALSE;
13863 DisplayMessage("",_("Close ICS engine analyze..."));
13865 if (first.analysisSupport && first.analyzing) {
13866 SendToProgram("exit\n", &first);
13867 first.analyzing = FALSE;
13869 thinkOutput[0] = NULLCHAR;
13873 EditPositionDone (Boolean fakeRights)
13875 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13877 startedFromSetupPosition = TRUE;
13878 InitChessProgram(&first, FALSE);
13879 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13880 boards[0][EP_STATUS] = EP_NONE;
13881 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13882 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13883 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13884 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13885 } else boards[0][CASTLING][2] = NoRights;
13886 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13887 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13888 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13889 } else boards[0][CASTLING][5] = NoRights;
13891 SendToProgram("force\n", &first);
13892 if (blackPlaysFirst) {
13893 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13894 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13895 currentMove = forwardMostMove = backwardMostMove = 1;
13896 CopyBoard(boards[1], boards[0]);
13898 currentMove = forwardMostMove = backwardMostMove = 0;
13900 SendBoard(&first, forwardMostMove);
13901 if (appData.debugMode) {
13902 fprintf(debugFP, "EditPosDone\n");
13905 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13906 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13907 gameMode = EditGame;
13909 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13910 ClearHighlights(); /* [AS] */
13913 /* Pause for `ms' milliseconds */
13914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13916 TimeDelay (long ms)
13923 } while (SubtractTimeMarks(&m2, &m1) < ms);
13926 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13928 SendMultiLineToICS (char *buf)
13930 char temp[MSG_SIZ+1], *p;
13937 strncpy(temp, buf, len);
13942 if (*p == '\n' || *p == '\r')
13947 strcat(temp, "\n");
13949 SendToPlayer(temp, strlen(temp));
13953 SetWhiteToPlayEvent ()
13955 if (gameMode == EditPosition) {
13956 blackPlaysFirst = FALSE;
13957 DisplayBothClocks(); /* works because currentMove is 0 */
13958 } else if (gameMode == IcsExamining) {
13959 SendToICS(ics_prefix);
13960 SendToICS("tomove white\n");
13965 SetBlackToPlayEvent ()
13967 if (gameMode == EditPosition) {
13968 blackPlaysFirst = TRUE;
13969 currentMove = 1; /* kludge */
13970 DisplayBothClocks();
13972 } else if (gameMode == IcsExamining) {
13973 SendToICS(ics_prefix);
13974 SendToICS("tomove black\n");
13979 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13982 ChessSquare piece = boards[0][y][x];
13984 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13986 switch (selection) {
13988 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13989 SendToICS(ics_prefix);
13990 SendToICS("bsetup clear\n");
13991 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13992 SendToICS(ics_prefix);
13993 SendToICS("clearboard\n");
13995 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13996 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13997 for (y = 0; y < BOARD_HEIGHT; y++) {
13998 if (gameMode == IcsExamining) {
13999 if (boards[currentMove][y][x] != EmptySquare) {
14000 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14005 boards[0][y][x] = p;
14010 if (gameMode == EditPosition) {
14011 DrawPosition(FALSE, boards[0]);
14016 SetWhiteToPlayEvent();
14020 SetBlackToPlayEvent();
14024 if (gameMode == IcsExamining) {
14025 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14026 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14029 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14030 if(x == BOARD_LEFT-2) {
14031 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14032 boards[0][y][1] = 0;
14034 if(x == BOARD_RGHT+1) {
14035 if(y >= gameInfo.holdingsSize) break;
14036 boards[0][y][BOARD_WIDTH-2] = 0;
14039 boards[0][y][x] = EmptySquare;
14040 DrawPosition(FALSE, boards[0]);
14045 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14046 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14047 selection = (ChessSquare) (PROMOTED piece);
14048 } else if(piece == EmptySquare) selection = WhiteSilver;
14049 else selection = (ChessSquare)((int)piece - 1);
14053 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14054 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14055 selection = (ChessSquare) (DEMOTED piece);
14056 } else if(piece == EmptySquare) selection = BlackSilver;
14057 else selection = (ChessSquare)((int)piece + 1);
14062 if(gameInfo.variant == VariantShatranj ||
14063 gameInfo.variant == VariantXiangqi ||
14064 gameInfo.variant == VariantCourier ||
14065 gameInfo.variant == VariantMakruk )
14066 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14071 if(gameInfo.variant == VariantXiangqi)
14072 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14073 if(gameInfo.variant == VariantKnightmate)
14074 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14077 if (gameMode == IcsExamining) {
14078 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14079 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14080 PieceToChar(selection), AAA + x, ONE + y);
14083 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14085 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14086 n = PieceToNumber(selection - BlackPawn);
14087 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14088 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14089 boards[0][BOARD_HEIGHT-1-n][1]++;
14091 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14092 n = PieceToNumber(selection);
14093 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14094 boards[0][n][BOARD_WIDTH-1] = selection;
14095 boards[0][n][BOARD_WIDTH-2]++;
14098 boards[0][y][x] = selection;
14099 DrawPosition(TRUE, boards[0]);
14101 fromX = fromY = -1;
14109 DropMenuEvent (ChessSquare selection, int x, int y)
14111 ChessMove moveType;
14113 switch (gameMode) {
14114 case IcsPlayingWhite:
14115 case MachinePlaysBlack:
14116 if (!WhiteOnMove(currentMove)) {
14117 DisplayMoveError(_("It is Black's turn"));
14120 moveType = WhiteDrop;
14122 case IcsPlayingBlack:
14123 case MachinePlaysWhite:
14124 if (WhiteOnMove(currentMove)) {
14125 DisplayMoveError(_("It is White's turn"));
14128 moveType = BlackDrop;
14131 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14137 if (moveType == BlackDrop && selection < BlackPawn) {
14138 selection = (ChessSquare) ((int) selection
14139 + (int) BlackPawn - (int) WhitePawn);
14141 if (boards[currentMove][y][x] != EmptySquare) {
14142 DisplayMoveError(_("That square is occupied"));
14146 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14152 /* Accept a pending offer of any kind from opponent */
14154 if (appData.icsActive) {
14155 SendToICS(ics_prefix);
14156 SendToICS("accept\n");
14157 } else if (cmailMsgLoaded) {
14158 if (currentMove == cmailOldMove &&
14159 commentList[cmailOldMove] != NULL &&
14160 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14161 "Black offers a draw" : "White offers a draw")) {
14163 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14164 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14166 DisplayError(_("There is no pending offer on this move"), 0);
14167 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14170 /* Not used for offers from chess program */
14177 /* Decline a pending offer of any kind from opponent */
14179 if (appData.icsActive) {
14180 SendToICS(ics_prefix);
14181 SendToICS("decline\n");
14182 } else if (cmailMsgLoaded) {
14183 if (currentMove == cmailOldMove &&
14184 commentList[cmailOldMove] != NULL &&
14185 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14186 "Black offers a draw" : "White offers a draw")) {
14188 AppendComment(cmailOldMove, "Draw declined", TRUE);
14189 DisplayComment(cmailOldMove - 1, "Draw declined");
14192 DisplayError(_("There is no pending offer on this move"), 0);
14195 /* Not used for offers from chess program */
14202 /* Issue ICS rematch command */
14203 if (appData.icsActive) {
14204 SendToICS(ics_prefix);
14205 SendToICS("rematch\n");
14212 /* Call your opponent's flag (claim a win on time) */
14213 if (appData.icsActive) {
14214 SendToICS(ics_prefix);
14215 SendToICS("flag\n");
14217 switch (gameMode) {
14220 case MachinePlaysWhite:
14223 GameEnds(GameIsDrawn, "Both players ran out of time",
14226 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14228 DisplayError(_("Your opponent is not out of time"), 0);
14231 case MachinePlaysBlack:
14234 GameEnds(GameIsDrawn, "Both players ran out of time",
14237 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14239 DisplayError(_("Your opponent is not out of time"), 0);
14247 ClockClick (int which)
14248 { // [HGM] code moved to back-end from winboard.c
14249 if(which) { // black clock
14250 if (gameMode == EditPosition || gameMode == IcsExamining) {
14251 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14252 SetBlackToPlayEvent();
14253 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14254 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14255 } else if (shiftKey) {
14256 AdjustClock(which, -1);
14257 } else if (gameMode == IcsPlayingWhite ||
14258 gameMode == MachinePlaysBlack) {
14261 } else { // white clock
14262 if (gameMode == EditPosition || gameMode == IcsExamining) {
14263 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14264 SetWhiteToPlayEvent();
14265 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14266 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14267 } else if (shiftKey) {
14268 AdjustClock(which, -1);
14269 } else if (gameMode == IcsPlayingBlack ||
14270 gameMode == MachinePlaysWhite) {
14279 /* Offer draw or accept pending draw offer from opponent */
14281 if (appData.icsActive) {
14282 /* Note: tournament rules require draw offers to be
14283 made after you make your move but before you punch
14284 your clock. Currently ICS doesn't let you do that;
14285 instead, you immediately punch your clock after making
14286 a move, but you can offer a draw at any time. */
14288 SendToICS(ics_prefix);
14289 SendToICS("draw\n");
14290 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14291 } else if (cmailMsgLoaded) {
14292 if (currentMove == cmailOldMove &&
14293 commentList[cmailOldMove] != NULL &&
14294 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14295 "Black offers a draw" : "White offers a draw")) {
14296 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14298 } else if (currentMove == cmailOldMove + 1) {
14299 char *offer = WhiteOnMove(cmailOldMove) ?
14300 "White offers a draw" : "Black offers a draw";
14301 AppendComment(currentMove, offer, TRUE);
14302 DisplayComment(currentMove - 1, offer);
14303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14305 DisplayError(_("You must make your move before offering a draw"), 0);
14306 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14308 } else if (first.offeredDraw) {
14309 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14311 if (first.sendDrawOffers) {
14312 SendToProgram("draw\n", &first);
14313 userOfferedDraw = TRUE;
14321 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14323 if (appData.icsActive) {
14324 SendToICS(ics_prefix);
14325 SendToICS("adjourn\n");
14327 /* Currently GNU Chess doesn't offer or accept Adjourns */
14335 /* Offer Abort or accept pending Abort offer from opponent */
14337 if (appData.icsActive) {
14338 SendToICS(ics_prefix);
14339 SendToICS("abort\n");
14341 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14348 /* Resign. You can do this even if it's not your turn. */
14350 if (appData.icsActive) {
14351 SendToICS(ics_prefix);
14352 SendToICS("resign\n");
14354 switch (gameMode) {
14355 case MachinePlaysWhite:
14356 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14358 case MachinePlaysBlack:
14359 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14362 if (cmailMsgLoaded) {
14364 if (WhiteOnMove(cmailOldMove)) {
14365 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14367 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14369 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14380 StopObservingEvent ()
14382 /* Stop observing current games */
14383 SendToICS(ics_prefix);
14384 SendToICS("unobserve\n");
14388 StopExaminingEvent ()
14390 /* Stop observing current game */
14391 SendToICS(ics_prefix);
14392 SendToICS("unexamine\n");
14396 ForwardInner (int target)
14398 int limit; int oldSeekGraphUp = seekGraphUp;
14400 if (appData.debugMode)
14401 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14402 target, currentMove, forwardMostMove);
14404 if (gameMode == EditPosition)
14407 seekGraphUp = FALSE;
14408 MarkTargetSquares(1);
14410 if (gameMode == PlayFromGameFile && !pausing)
14413 if (gameMode == IcsExamining && pausing)
14414 limit = pauseExamForwardMostMove;
14416 limit = forwardMostMove;
14418 if (target > limit) target = limit;
14420 if (target > 0 && moveList[target - 1][0]) {
14421 int fromX, fromY, toX, toY;
14422 toX = moveList[target - 1][2] - AAA;
14423 toY = moveList[target - 1][3] - ONE;
14424 if (moveList[target - 1][1] == '@') {
14425 if (appData.highlightLastMove) {
14426 SetHighlights(-1, -1, toX, toY);
14429 fromX = moveList[target - 1][0] - AAA;
14430 fromY = moveList[target - 1][1] - ONE;
14431 if (target == currentMove + 1) {
14432 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14434 if (appData.highlightLastMove) {
14435 SetHighlights(fromX, fromY, toX, toY);
14439 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14440 gameMode == Training || gameMode == PlayFromGameFile ||
14441 gameMode == AnalyzeFile) {
14442 while (currentMove < target) {
14443 SendMoveToProgram(currentMove++, &first);
14446 currentMove = target;
14449 if (gameMode == EditGame || gameMode == EndOfGame) {
14450 whiteTimeRemaining = timeRemaining[0][currentMove];
14451 blackTimeRemaining = timeRemaining[1][currentMove];
14453 DisplayBothClocks();
14454 DisplayMove(currentMove - 1);
14455 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14456 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14457 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14458 DisplayComment(currentMove - 1, commentList[currentMove]);
14460 ClearMap(); // [HGM] exclude: invalidate map
14467 if (gameMode == IcsExamining && !pausing) {
14468 SendToICS(ics_prefix);
14469 SendToICS("forward\n");
14471 ForwardInner(currentMove + 1);
14478 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14479 /* to optimze, we temporarily turn off analysis mode while we feed
14480 * the remaining moves to the engine. Otherwise we get analysis output
14483 if (first.analysisSupport) {
14484 SendToProgram("exit\nforce\n", &first);
14485 first.analyzing = FALSE;
14489 if (gameMode == IcsExamining && !pausing) {
14490 SendToICS(ics_prefix);
14491 SendToICS("forward 999999\n");
14493 ForwardInner(forwardMostMove);
14496 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14497 /* we have fed all the moves, so reactivate analysis mode */
14498 SendToProgram("analyze\n", &first);
14499 first.analyzing = TRUE;
14500 /*first.maybeThinking = TRUE;*/
14501 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14506 BackwardInner (int target)
14508 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14510 if (appData.debugMode)
14511 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14512 target, currentMove, forwardMostMove);
14514 if (gameMode == EditPosition) return;
14515 seekGraphUp = FALSE;
14516 MarkTargetSquares(1);
14517 if (currentMove <= backwardMostMove) {
14519 DrawPosition(full_redraw, boards[currentMove]);
14522 if (gameMode == PlayFromGameFile && !pausing)
14525 if (moveList[target][0]) {
14526 int fromX, fromY, toX, toY;
14527 toX = moveList[target][2] - AAA;
14528 toY = moveList[target][3] - ONE;
14529 if (moveList[target][1] == '@') {
14530 if (appData.highlightLastMove) {
14531 SetHighlights(-1, -1, toX, toY);
14534 fromX = moveList[target][0] - AAA;
14535 fromY = moveList[target][1] - ONE;
14536 if (target == currentMove - 1) {
14537 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14539 if (appData.highlightLastMove) {
14540 SetHighlights(fromX, fromY, toX, toY);
14544 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14545 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14546 while (currentMove > target) {
14547 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14548 // null move cannot be undone. Reload program with move history before it.
14550 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14551 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14553 SendBoard(&first, i);
14554 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14557 SendToProgram("undo\n", &first);
14561 currentMove = target;
14564 if (gameMode == EditGame || gameMode == EndOfGame) {
14565 whiteTimeRemaining = timeRemaining[0][currentMove];
14566 blackTimeRemaining = timeRemaining[1][currentMove];
14568 DisplayBothClocks();
14569 DisplayMove(currentMove - 1);
14570 DrawPosition(full_redraw, boards[currentMove]);
14571 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14572 // [HGM] PV info: routine tests if comment empty
14573 DisplayComment(currentMove - 1, commentList[currentMove]);
14574 ClearMap(); // [HGM] exclude: invalidate map
14580 if (gameMode == IcsExamining && !pausing) {
14581 SendToICS(ics_prefix);
14582 SendToICS("backward\n");
14584 BackwardInner(currentMove - 1);
14591 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14592 /* to optimize, we temporarily turn off analysis mode while we undo
14593 * all the moves. Otherwise we get analysis output after each undo.
14595 if (first.analysisSupport) {
14596 SendToProgram("exit\nforce\n", &first);
14597 first.analyzing = FALSE;
14601 if (gameMode == IcsExamining && !pausing) {
14602 SendToICS(ics_prefix);
14603 SendToICS("backward 999999\n");
14605 BackwardInner(backwardMostMove);
14608 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14609 /* we have fed all the moves, so reactivate analysis mode */
14610 SendToProgram("analyze\n", &first);
14611 first.analyzing = TRUE;
14612 /*first.maybeThinking = TRUE;*/
14613 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14620 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14621 if (to >= forwardMostMove) to = forwardMostMove;
14622 if (to <= backwardMostMove) to = backwardMostMove;
14623 if (to < currentMove) {
14631 RevertEvent (Boolean annotate)
14633 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14636 if (gameMode != IcsExamining) {
14637 DisplayError(_("You are not examining a game"), 0);
14641 DisplayError(_("You can't revert while pausing"), 0);
14644 SendToICS(ics_prefix);
14645 SendToICS("revert\n");
14649 RetractMoveEvent ()
14651 switch (gameMode) {
14652 case MachinePlaysWhite:
14653 case MachinePlaysBlack:
14654 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14655 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14658 if (forwardMostMove < 2) return;
14659 currentMove = forwardMostMove = forwardMostMove - 2;
14660 whiteTimeRemaining = timeRemaining[0][currentMove];
14661 blackTimeRemaining = timeRemaining[1][currentMove];
14662 DisplayBothClocks();
14663 DisplayMove(currentMove - 1);
14664 ClearHighlights();/*!! could figure this out*/
14665 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14666 SendToProgram("remove\n", &first);
14667 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14670 case BeginningOfGame:
14674 case IcsPlayingWhite:
14675 case IcsPlayingBlack:
14676 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14677 SendToICS(ics_prefix);
14678 SendToICS("takeback 2\n");
14680 SendToICS(ics_prefix);
14681 SendToICS("takeback 1\n");
14690 ChessProgramState *cps;
14692 switch (gameMode) {
14693 case MachinePlaysWhite:
14694 if (!WhiteOnMove(forwardMostMove)) {
14695 DisplayError(_("It is your turn"), 0);
14700 case MachinePlaysBlack:
14701 if (WhiteOnMove(forwardMostMove)) {
14702 DisplayError(_("It is your turn"), 0);
14707 case TwoMachinesPlay:
14708 if (WhiteOnMove(forwardMostMove) ==
14709 (first.twoMachinesColor[0] == 'w')) {
14715 case BeginningOfGame:
14719 SendToProgram("?\n", cps);
14723 TruncateGameEvent ()
14726 if (gameMode != EditGame) return;
14733 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14734 if (forwardMostMove > currentMove) {
14735 if (gameInfo.resultDetails != NULL) {
14736 free(gameInfo.resultDetails);
14737 gameInfo.resultDetails = NULL;
14738 gameInfo.result = GameUnfinished;
14740 forwardMostMove = currentMove;
14741 HistorySet(parseList, backwardMostMove, forwardMostMove,
14749 if (appData.noChessProgram) return;
14750 switch (gameMode) {
14751 case MachinePlaysWhite:
14752 if (WhiteOnMove(forwardMostMove)) {
14753 DisplayError(_("Wait until your turn"), 0);
14757 case BeginningOfGame:
14758 case MachinePlaysBlack:
14759 if (!WhiteOnMove(forwardMostMove)) {
14760 DisplayError(_("Wait until your turn"), 0);
14765 DisplayError(_("No hint available"), 0);
14768 SendToProgram("hint\n", &first);
14769 hintRequested = TRUE;
14775 if (appData.noChessProgram) return;
14776 switch (gameMode) {
14777 case MachinePlaysWhite:
14778 if (WhiteOnMove(forwardMostMove)) {
14779 DisplayError(_("Wait until your turn"), 0);
14783 case BeginningOfGame:
14784 case MachinePlaysBlack:
14785 if (!WhiteOnMove(forwardMostMove)) {
14786 DisplayError(_("Wait until your turn"), 0);
14791 EditPositionDone(TRUE);
14793 case TwoMachinesPlay:
14798 SendToProgram("bk\n", &first);
14799 bookOutput[0] = NULLCHAR;
14800 bookRequested = TRUE;
14806 char *tags = PGNTags(&gameInfo);
14807 TagsPopUp(tags, CmailMsg());
14811 /* end button procedures */
14814 PrintPosition (FILE *fp, int move)
14818 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14819 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14820 char c = PieceToChar(boards[move][i][j]);
14821 fputc(c == 'x' ? '.' : c, fp);
14822 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14825 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14826 fprintf(fp, "white to play\n");
14828 fprintf(fp, "black to play\n");
14832 PrintOpponents (FILE *fp)
14834 if (gameInfo.white != NULL) {
14835 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14841 /* Find last component of program's own name, using some heuristics */
14843 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14846 int local = (strcmp(host, "localhost") == 0);
14847 while (!local && (p = strchr(prog, ';')) != NULL) {
14849 while (*p == ' ') p++;
14852 if (*prog == '"' || *prog == '\'') {
14853 q = strchr(prog + 1, *prog);
14855 q = strchr(prog, ' ');
14857 if (q == NULL) q = prog + strlen(prog);
14859 while (p >= prog && *p != '/' && *p != '\\') p--;
14861 if(p == prog && *p == '"') p++;
14863 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14864 memcpy(buf, p, q - p);
14865 buf[q - p] = NULLCHAR;
14873 TimeControlTagValue ()
14876 if (!appData.clockMode) {
14877 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14878 } else if (movesPerSession > 0) {
14879 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14880 } else if (timeIncrement == 0) {
14881 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14883 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14885 return StrSave(buf);
14891 /* This routine is used only for certain modes */
14892 VariantClass v = gameInfo.variant;
14893 ChessMove r = GameUnfinished;
14896 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14897 r = gameInfo.result;
14898 p = gameInfo.resultDetails;
14899 gameInfo.resultDetails = NULL;
14901 ClearGameInfo(&gameInfo);
14902 gameInfo.variant = v;
14904 switch (gameMode) {
14905 case MachinePlaysWhite:
14906 gameInfo.event = StrSave( appData.pgnEventHeader );
14907 gameInfo.site = StrSave(HostName());
14908 gameInfo.date = PGNDate();
14909 gameInfo.round = StrSave("-");
14910 gameInfo.white = StrSave(first.tidy);
14911 gameInfo.black = StrSave(UserName());
14912 gameInfo.timeControl = TimeControlTagValue();
14915 case MachinePlaysBlack:
14916 gameInfo.event = StrSave( appData.pgnEventHeader );
14917 gameInfo.site = StrSave(HostName());
14918 gameInfo.date = PGNDate();
14919 gameInfo.round = StrSave("-");
14920 gameInfo.white = StrSave(UserName());
14921 gameInfo.black = StrSave(first.tidy);
14922 gameInfo.timeControl = TimeControlTagValue();
14925 case TwoMachinesPlay:
14926 gameInfo.event = StrSave( appData.pgnEventHeader );
14927 gameInfo.site = StrSave(HostName());
14928 gameInfo.date = PGNDate();
14931 snprintf(buf, MSG_SIZ, "%d", roundNr);
14932 gameInfo.round = StrSave(buf);
14934 gameInfo.round = StrSave("-");
14936 if (first.twoMachinesColor[0] == 'w') {
14937 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14938 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14940 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14941 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14943 gameInfo.timeControl = TimeControlTagValue();
14947 gameInfo.event = StrSave("Edited game");
14948 gameInfo.site = StrSave(HostName());
14949 gameInfo.date = PGNDate();
14950 gameInfo.round = StrSave("-");
14951 gameInfo.white = StrSave("-");
14952 gameInfo.black = StrSave("-");
14953 gameInfo.result = r;
14954 gameInfo.resultDetails = p;
14958 gameInfo.event = StrSave("Edited position");
14959 gameInfo.site = StrSave(HostName());
14960 gameInfo.date = PGNDate();
14961 gameInfo.round = StrSave("-");
14962 gameInfo.white = StrSave("-");
14963 gameInfo.black = StrSave("-");
14966 case IcsPlayingWhite:
14967 case IcsPlayingBlack:
14972 case PlayFromGameFile:
14973 gameInfo.event = StrSave("Game from non-PGN file");
14974 gameInfo.site = StrSave(HostName());
14975 gameInfo.date = PGNDate();
14976 gameInfo.round = StrSave("-");
14977 gameInfo.white = StrSave("?");
14978 gameInfo.black = StrSave("?");
14987 ReplaceComment (int index, char *text)
14993 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14994 pvInfoList[index-1].depth == len &&
14995 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14996 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14997 while (*text == '\n') text++;
14998 len = strlen(text);
14999 while (len > 0 && text[len - 1] == '\n') len--;
15001 if (commentList[index] != NULL)
15002 free(commentList[index]);
15005 commentList[index] = NULL;
15008 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15009 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15010 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15011 commentList[index] = (char *) malloc(len + 2);
15012 strncpy(commentList[index], text, len);
15013 commentList[index][len] = '\n';
15014 commentList[index][len + 1] = NULLCHAR;
15016 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15018 commentList[index] = (char *) malloc(len + 7);
15019 safeStrCpy(commentList[index], "{\n", 3);
15020 safeStrCpy(commentList[index]+2, text, len+1);
15021 commentList[index][len+2] = NULLCHAR;
15022 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15023 strcat(commentList[index], "\n}\n");
15028 CrushCRs (char *text)
15036 if (ch == '\r') continue;
15038 } while (ch != '\0');
15042 AppendComment (int index, char *text, Boolean addBraces)
15043 /* addBraces tells if we should add {} */
15048 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15049 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15052 while (*text == '\n') text++;
15053 len = strlen(text);
15054 while (len > 0 && text[len - 1] == '\n') len--;
15055 text[len] = NULLCHAR;
15057 if (len == 0) return;
15059 if (commentList[index] != NULL) {
15060 Boolean addClosingBrace = addBraces;
15061 old = commentList[index];
15062 oldlen = strlen(old);
15063 while(commentList[index][oldlen-1] == '\n')
15064 commentList[index][--oldlen] = NULLCHAR;
15065 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15066 safeStrCpy(commentList[index], old, oldlen + len + 6);
15068 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15069 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15070 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15071 while (*text == '\n') { text++; len--; }
15072 commentList[index][--oldlen] = NULLCHAR;
15074 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15075 else strcat(commentList[index], "\n");
15076 strcat(commentList[index], text);
15077 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15078 else strcat(commentList[index], "\n");
15080 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15082 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15083 else commentList[index][0] = NULLCHAR;
15084 strcat(commentList[index], text);
15085 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15086 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15091 FindStr (char * text, char * sub_text)
15093 char * result = strstr( text, sub_text );
15095 if( result != NULL ) {
15096 result += strlen( sub_text );
15102 /* [AS] Try to extract PV info from PGN comment */
15103 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15105 GetInfoFromComment (int index, char * text)
15107 char * sep = text, *p;
15109 if( text != NULL && index > 0 ) {
15112 int time = -1, sec = 0, deci;
15113 char * s_eval = FindStr( text, "[%eval " );
15114 char * s_emt = FindStr( text, "[%emt " );
15116 if( s_eval != NULL || s_emt != NULL ) {
15120 if( s_eval != NULL ) {
15121 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15125 if( delim != ']' ) {
15130 if( s_emt != NULL ) {
15135 /* We expect something like: [+|-]nnn.nn/dd */
15138 if(*text != '{') return text; // [HGM] braces: must be normal comment
15140 sep = strchr( text, '/' );
15141 if( sep == NULL || sep < (text+4) ) {
15146 if(p[1] == '(') { // comment starts with PV
15147 p = strchr(p, ')'); // locate end of PV
15148 if(p == NULL || sep < p+5) return text;
15149 // at this point we have something like "{(.*) +0.23/6 ..."
15150 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15151 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15152 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15154 time = -1; sec = -1; deci = -1;
15155 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15156 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15157 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15158 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15162 if( score_lo < 0 || score_lo >= 100 ) {
15166 if(sec >= 0) time = 600*time + 10*sec; else
15167 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15169 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15171 /* [HGM] PV time: now locate end of PV info */
15172 while( *++sep >= '0' && *sep <= '9'); // strip depth
15174 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15176 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15178 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15179 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15190 pvInfoList[index-1].depth = depth;
15191 pvInfoList[index-1].score = score;
15192 pvInfoList[index-1].time = 10*time; // centi-sec
15193 if(*sep == '}') *sep = 0; else *--sep = '{';
15194 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15200 SendToProgram (char *message, ChessProgramState *cps)
15202 int count, outCount, error;
15205 if (cps->pr == NoProc) return;
15208 if (appData.debugMode) {
15211 fprintf(debugFP, "%ld >%-6s: %s",
15212 SubtractTimeMarks(&now, &programStartTime),
15213 cps->which, message);
15215 fprintf(serverFP, "%ld >%-6s: %s",
15216 SubtractTimeMarks(&now, &programStartTime),
15217 cps->which, message), fflush(serverFP);
15220 count = strlen(message);
15221 outCount = OutputToProcess(cps->pr, message, count, &error);
15222 if (outCount < count && !exiting
15223 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15224 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15225 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15226 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15227 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15228 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15229 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15230 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15232 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15233 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15234 gameInfo.result = res;
15236 gameInfo.resultDetails = StrSave(buf);
15238 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15239 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15244 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15248 ChessProgramState *cps = (ChessProgramState *)closure;
15250 if (isr != cps->isr) return; /* Killed intentionally */
15253 RemoveInputSource(cps->isr);
15254 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15255 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15256 _(cps->which), cps->program);
15257 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15258 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15259 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15260 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15261 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15263 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15264 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15265 gameInfo.result = res;
15267 gameInfo.resultDetails = StrSave(buf);
15269 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15270 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15272 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15273 _(cps->which), cps->program);
15274 RemoveInputSource(cps->isr);
15276 /* [AS] Program is misbehaving badly... kill it */
15277 if( count == -2 ) {
15278 DestroyChildProcess( cps->pr, 9 );
15282 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15287 if ((end_str = strchr(message, '\r')) != NULL)
15288 *end_str = NULLCHAR;
15289 if ((end_str = strchr(message, '\n')) != NULL)
15290 *end_str = NULLCHAR;
15292 if (appData.debugMode) {
15293 TimeMark now; int print = 1;
15294 char *quote = ""; char c; int i;
15296 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15297 char start = message[0];
15298 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15299 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15300 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15301 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15302 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15303 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15304 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15305 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15306 sscanf(message, "hint: %c", &c)!=1 &&
15307 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15308 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15309 print = (appData.engineComments >= 2);
15311 message[0] = start; // restore original message
15315 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15316 SubtractTimeMarks(&now, &programStartTime), cps->which,
15320 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15321 SubtractTimeMarks(&now, &programStartTime), cps->which,
15323 message), fflush(serverFP);
15327 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15328 if (appData.icsEngineAnalyze) {
15329 if (strstr(message, "whisper") != NULL ||
15330 strstr(message, "kibitz") != NULL ||
15331 strstr(message, "tellics") != NULL) return;
15334 HandleMachineMove(message, cps);
15339 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15344 if( timeControl_2 > 0 ) {
15345 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15346 tc = timeControl_2;
15349 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15350 inc /= cps->timeOdds;
15351 st /= cps->timeOdds;
15353 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15356 /* Set exact time per move, normally using st command */
15357 if (cps->stKludge) {
15358 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15360 if (seconds == 0) {
15361 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15363 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15366 snprintf(buf, MSG_SIZ, "st %d\n", st);
15369 /* Set conventional or incremental time control, using level command */
15370 if (seconds == 0) {
15371 /* Note old gnuchess bug -- minutes:seconds used to not work.
15372 Fixed in later versions, but still avoid :seconds
15373 when seconds is 0. */
15374 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15376 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15377 seconds, inc/1000.);
15380 SendToProgram(buf, cps);
15382 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15383 /* Orthogonally, limit search to given depth */
15385 if (cps->sdKludge) {
15386 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15388 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15390 SendToProgram(buf, cps);
15393 if(cps->nps >= 0) { /* [HGM] nps */
15394 if(cps->supportsNPS == FALSE)
15395 cps->nps = -1; // don't use if engine explicitly says not supported!
15397 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15398 SendToProgram(buf, cps);
15403 ChessProgramState *
15405 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15407 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15408 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15414 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15416 char message[MSG_SIZ];
15419 /* Note: this routine must be called when the clocks are stopped
15420 or when they have *just* been set or switched; otherwise
15421 it will be off by the time since the current tick started.
15423 if (machineWhite) {
15424 time = whiteTimeRemaining / 10;
15425 otime = blackTimeRemaining / 10;
15427 time = blackTimeRemaining / 10;
15428 otime = whiteTimeRemaining / 10;
15430 /* [HGM] translate opponent's time by time-odds factor */
15431 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15433 if (time <= 0) time = 1;
15434 if (otime <= 0) otime = 1;
15436 snprintf(message, MSG_SIZ, "time %ld\n", time);
15437 SendToProgram(message, cps);
15439 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15440 SendToProgram(message, cps);
15444 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15447 int len = strlen(name);
15450 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15452 sscanf(*p, "%d", &val);
15454 while (**p && **p != ' ')
15456 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15457 SendToProgram(buf, cps);
15464 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15467 int len = strlen(name);
15468 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15470 sscanf(*p, "%d", loc);
15471 while (**p && **p != ' ') (*p)++;
15472 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15473 SendToProgram(buf, cps);
15480 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15483 int len = strlen(name);
15484 if (strncmp((*p), name, len) == 0
15485 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15487 sscanf(*p, "%[^\"]", loc);
15488 while (**p && **p != '\"') (*p)++;
15489 if (**p == '\"') (*p)++;
15490 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15491 SendToProgram(buf, cps);
15498 ParseOption (Option *opt, ChessProgramState *cps)
15499 // [HGM] options: process the string that defines an engine option, and determine
15500 // name, type, default value, and allowed value range
15502 char *p, *q, buf[MSG_SIZ];
15503 int n, min = (-1)<<31, max = 1<<31, def;
15505 if(p = strstr(opt->name, " -spin ")) {
15506 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15507 if(max < min) max = min; // enforce consistency
15508 if(def < min) def = min;
15509 if(def > max) def = max;
15514 } else if((p = strstr(opt->name, " -slider "))) {
15515 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15516 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15517 if(max < min) max = min; // enforce consistency
15518 if(def < min) def = min;
15519 if(def > max) def = max;
15523 opt->type = Spin; // Slider;
15524 } else if((p = strstr(opt->name, " -string "))) {
15525 opt->textValue = p+9;
15526 opt->type = TextBox;
15527 } else if((p = strstr(opt->name, " -file "))) {
15528 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15529 opt->textValue = p+7;
15530 opt->type = FileName; // FileName;
15531 } else if((p = strstr(opt->name, " -path "))) {
15532 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15533 opt->textValue = p+7;
15534 opt->type = PathName; // PathName;
15535 } else if(p = strstr(opt->name, " -check ")) {
15536 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15537 opt->value = (def != 0);
15538 opt->type = CheckBox;
15539 } else if(p = strstr(opt->name, " -combo ")) {
15540 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15541 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15542 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15543 opt->value = n = 0;
15544 while(q = StrStr(q, " /// ")) {
15545 n++; *q = 0; // count choices, and null-terminate each of them
15547 if(*q == '*') { // remember default, which is marked with * prefix
15551 cps->comboList[cps->comboCnt++] = q;
15553 cps->comboList[cps->comboCnt++] = NULL;
15555 opt->type = ComboBox;
15556 } else if(p = strstr(opt->name, " -button")) {
15557 opt->type = Button;
15558 } else if(p = strstr(opt->name, " -save")) {
15559 opt->type = SaveButton;
15560 } else return FALSE;
15561 *p = 0; // terminate option name
15562 // now look if the command-line options define a setting for this engine option.
15563 if(cps->optionSettings && cps->optionSettings[0])
15564 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15565 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15566 snprintf(buf, MSG_SIZ, "option %s", p);
15567 if(p = strstr(buf, ",")) *p = 0;
15568 if(q = strchr(buf, '=')) switch(opt->type) {
15570 for(n=0; n<opt->max; n++)
15571 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15574 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15578 opt->value = atoi(q+1);
15583 SendToProgram(buf, cps);
15589 FeatureDone (ChessProgramState *cps, int val)
15591 DelayedEventCallback cb = GetDelayedEvent();
15592 if ((cb == InitBackEnd3 && cps == &first) ||
15593 (cb == SettingsMenuIfReady && cps == &second) ||
15594 (cb == LoadEngine) ||
15595 (cb == TwoMachinesEventIfReady)) {
15596 CancelDelayedEvent();
15597 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15599 cps->initDone = val;
15602 /* Parse feature command from engine */
15604 ParseFeatures (char *args, ChessProgramState *cps)
15612 while (*p == ' ') p++;
15613 if (*p == NULLCHAR) return;
15615 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15616 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15617 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15618 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15619 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15620 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15621 if (BoolFeature(&p, "reuse", &val, cps)) {
15622 /* Engine can disable reuse, but can't enable it if user said no */
15623 if (!val) cps->reuse = FALSE;
15626 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15627 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15628 if (gameMode == TwoMachinesPlay) {
15629 DisplayTwoMachinesTitle();
15635 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15636 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15637 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15638 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15639 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15640 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15641 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15642 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15643 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15644 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15645 if (IntFeature(&p, "done", &val, cps)) {
15646 FeatureDone(cps, val);
15649 /* Added by Tord: */
15650 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15651 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15652 /* End of additions by Tord */
15654 /* [HGM] added features: */
15655 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15656 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15657 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15658 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15659 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15660 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15661 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15662 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15663 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15664 SendToProgram(buf, cps);
15667 if(cps->nrOptions >= MAX_OPTIONS) {
15669 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15670 DisplayError(buf, 0);
15674 /* End of additions by HGM */
15676 /* unknown feature: complain and skip */
15678 while (*q && *q != '=') q++;
15679 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15680 SendToProgram(buf, cps);
15686 while (*p && *p != '\"') p++;
15687 if (*p == '\"') p++;
15689 while (*p && *p != ' ') p++;
15697 PeriodicUpdatesEvent (int newState)
15699 if (newState == appData.periodicUpdates)
15702 appData.periodicUpdates=newState;
15704 /* Display type changes, so update it now */
15705 // DisplayAnalysis();
15707 /* Get the ball rolling again... */
15709 AnalysisPeriodicEvent(1);
15710 StartAnalysisClock();
15715 PonderNextMoveEvent (int newState)
15717 if (newState == appData.ponderNextMove) return;
15718 if (gameMode == EditPosition) EditPositionDone(TRUE);
15720 SendToProgram("hard\n", &first);
15721 if (gameMode == TwoMachinesPlay) {
15722 SendToProgram("hard\n", &second);
15725 SendToProgram("easy\n", &first);
15726 thinkOutput[0] = NULLCHAR;
15727 if (gameMode == TwoMachinesPlay) {
15728 SendToProgram("easy\n", &second);
15731 appData.ponderNextMove = newState;
15735 NewSettingEvent (int option, int *feature, char *command, int value)
15739 if (gameMode == EditPosition) EditPositionDone(TRUE);
15740 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15741 if(feature == NULL || *feature) SendToProgram(buf, &first);
15742 if (gameMode == TwoMachinesPlay) {
15743 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15748 ShowThinkingEvent ()
15749 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15751 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15752 int newState = appData.showThinking
15753 // [HGM] thinking: other features now need thinking output as well
15754 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15756 if (oldState == newState) return;
15757 oldState = newState;
15758 if (gameMode == EditPosition) EditPositionDone(TRUE);
15760 SendToProgram("post\n", &first);
15761 if (gameMode == TwoMachinesPlay) {
15762 SendToProgram("post\n", &second);
15765 SendToProgram("nopost\n", &first);
15766 thinkOutput[0] = NULLCHAR;
15767 if (gameMode == TwoMachinesPlay) {
15768 SendToProgram("nopost\n", &second);
15771 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15775 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15777 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15778 if (pr == NoProc) return;
15779 AskQuestion(title, question, replyPrefix, pr);
15783 TypeInEvent (char firstChar)
15785 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15786 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15787 gameMode == AnalyzeMode || gameMode == EditGame ||
15788 gameMode == EditPosition || gameMode == IcsExamining ||
15789 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15790 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15791 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15792 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15793 gameMode == Training) PopUpMoveDialog(firstChar);
15797 TypeInDoneEvent (char *move)
15800 int n, fromX, fromY, toX, toY;
15802 ChessMove moveType;
15805 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15806 EditPositionPasteFEN(move);
15809 // [HGM] movenum: allow move number to be typed in any mode
15810 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15814 // undocumented kludge: allow command-line option to be typed in!
15815 // (potentially fatal, and does not implement the effect of the option.)
15816 // should only be used for options that are values on which future decisions will be made,
15817 // and definitely not on options that would be used during initialization.
15818 if(strstr(move, "!!! -") == move) {
15819 ParseArgsFromString(move+4);
15823 if (gameMode != EditGame && currentMove != forwardMostMove &&
15824 gameMode != Training) {
15825 DisplayMoveError(_("Displayed move is not current"));
15827 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15828 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15829 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15830 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15831 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15832 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15834 DisplayMoveError(_("Could not parse move"));
15840 DisplayMove (int moveNumber)
15842 char message[MSG_SIZ];
15844 char cpThinkOutput[MSG_SIZ];
15846 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15848 if (moveNumber == forwardMostMove - 1 ||
15849 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15851 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15853 if (strchr(cpThinkOutput, '\n')) {
15854 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15857 *cpThinkOutput = NULLCHAR;
15860 /* [AS] Hide thinking from human user */
15861 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15862 *cpThinkOutput = NULLCHAR;
15863 if( thinkOutput[0] != NULLCHAR ) {
15866 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15867 cpThinkOutput[i] = '.';
15869 cpThinkOutput[i] = NULLCHAR;
15870 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15874 if (moveNumber == forwardMostMove - 1 &&
15875 gameInfo.resultDetails != NULL) {
15876 if (gameInfo.resultDetails[0] == NULLCHAR) {
15877 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15879 snprintf(res, MSG_SIZ, " {%s} %s",
15880 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15886 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15887 DisplayMessage(res, cpThinkOutput);
15889 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15890 WhiteOnMove(moveNumber) ? " " : ".. ",
15891 parseList[moveNumber], res);
15892 DisplayMessage(message, cpThinkOutput);
15897 DisplayComment (int moveNumber, char *text)
15899 char title[MSG_SIZ];
15901 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15902 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15904 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15905 WhiteOnMove(moveNumber) ? " " : ".. ",
15906 parseList[moveNumber]);
15908 if (text != NULL && (appData.autoDisplayComment || commentUp))
15909 CommentPopUp(title, text);
15912 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15913 * might be busy thinking or pondering. It can be omitted if your
15914 * gnuchess is configured to stop thinking immediately on any user
15915 * input. However, that gnuchess feature depends on the FIONREAD
15916 * ioctl, which does not work properly on some flavors of Unix.
15919 Attention (ChessProgramState *cps)
15922 if (!cps->useSigint) return;
15923 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15924 switch (gameMode) {
15925 case MachinePlaysWhite:
15926 case MachinePlaysBlack:
15927 case TwoMachinesPlay:
15928 case IcsPlayingWhite:
15929 case IcsPlayingBlack:
15932 /* Skip if we know it isn't thinking */
15933 if (!cps->maybeThinking) return;
15934 if (appData.debugMode)
15935 fprintf(debugFP, "Interrupting %s\n", cps->which);
15936 InterruptChildProcess(cps->pr);
15937 cps->maybeThinking = FALSE;
15942 #endif /*ATTENTION*/
15948 if (whiteTimeRemaining <= 0) {
15951 if (appData.icsActive) {
15952 if (appData.autoCallFlag &&
15953 gameMode == IcsPlayingBlack && !blackFlag) {
15954 SendToICS(ics_prefix);
15955 SendToICS("flag\n");
15959 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15961 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15962 if (appData.autoCallFlag) {
15963 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15970 if (blackTimeRemaining <= 0) {
15973 if (appData.icsActive) {
15974 if (appData.autoCallFlag &&
15975 gameMode == IcsPlayingWhite && !whiteFlag) {
15976 SendToICS(ics_prefix);
15977 SendToICS("flag\n");
15981 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15983 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15984 if (appData.autoCallFlag) {
15985 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15996 CheckTimeControl ()
15998 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15999 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16002 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16004 if ( !WhiteOnMove(forwardMostMove) ) {
16005 /* White made time control */
16006 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16007 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16008 /* [HGM] time odds: correct new time quota for time odds! */
16009 / WhitePlayer()->timeOdds;
16010 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16012 lastBlack -= blackTimeRemaining;
16013 /* Black made time control */
16014 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16015 / WhitePlayer()->other->timeOdds;
16016 lastWhite = whiteTimeRemaining;
16021 DisplayBothClocks ()
16023 int wom = gameMode == EditPosition ?
16024 !blackPlaysFirst : WhiteOnMove(currentMove);
16025 DisplayWhiteClock(whiteTimeRemaining, wom);
16026 DisplayBlackClock(blackTimeRemaining, !wom);
16030 /* Timekeeping seems to be a portability nightmare. I think everyone
16031 has ftime(), but I'm really not sure, so I'm including some ifdefs
16032 to use other calls if you don't. Clocks will be less accurate if
16033 you have neither ftime nor gettimeofday.
16036 /* VS 2008 requires the #include outside of the function */
16037 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16038 #include <sys/timeb.h>
16041 /* Get the current time as a TimeMark */
16043 GetTimeMark (TimeMark *tm)
16045 #if HAVE_GETTIMEOFDAY
16047 struct timeval timeVal;
16048 struct timezone timeZone;
16050 gettimeofday(&timeVal, &timeZone);
16051 tm->sec = (long) timeVal.tv_sec;
16052 tm->ms = (int) (timeVal.tv_usec / 1000L);
16054 #else /*!HAVE_GETTIMEOFDAY*/
16057 // include <sys/timeb.h> / moved to just above start of function
16058 struct timeb timeB;
16061 tm->sec = (long) timeB.time;
16062 tm->ms = (int) timeB.millitm;
16064 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16065 tm->sec = (long) time(NULL);
16071 /* Return the difference in milliseconds between two
16072 time marks. We assume the difference will fit in a long!
16075 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16077 return 1000L*(tm2->sec - tm1->sec) +
16078 (long) (tm2->ms - tm1->ms);
16083 * Code to manage the game clocks.
16085 * In tournament play, black starts the clock and then white makes a move.
16086 * We give the human user a slight advantage if he is playing white---the
16087 * clocks don't run until he makes his first move, so it takes zero time.
16088 * Also, we don't account for network lag, so we could get out of sync
16089 * with GNU Chess's clock -- but then, referees are always right.
16092 static TimeMark tickStartTM;
16093 static long intendedTickLength;
16096 NextTickLength (long timeRemaining)
16098 long nominalTickLength, nextTickLength;
16100 if (timeRemaining > 0L && timeRemaining <= 10000L)
16101 nominalTickLength = 100L;
16103 nominalTickLength = 1000L;
16104 nextTickLength = timeRemaining % nominalTickLength;
16105 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16107 return nextTickLength;
16110 /* Adjust clock one minute up or down */
16112 AdjustClock (Boolean which, int dir)
16114 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16115 if(which) blackTimeRemaining += 60000*dir;
16116 else whiteTimeRemaining += 60000*dir;
16117 DisplayBothClocks();
16118 adjustedClock = TRUE;
16121 /* Stop clocks and reset to a fresh time control */
16125 (void) StopClockTimer();
16126 if (appData.icsActive) {
16127 whiteTimeRemaining = blackTimeRemaining = 0;
16128 } else if (searchTime) {
16129 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16130 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16131 } else { /* [HGM] correct new time quote for time odds */
16132 whiteTC = blackTC = fullTimeControlString;
16133 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16134 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16136 if (whiteFlag || blackFlag) {
16138 whiteFlag = blackFlag = FALSE;
16140 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16141 DisplayBothClocks();
16142 adjustedClock = FALSE;
16145 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16147 /* Decrement running clock by amount of time that has passed */
16151 long timeRemaining;
16152 long lastTickLength, fudge;
16155 if (!appData.clockMode) return;
16156 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16160 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16162 /* Fudge if we woke up a little too soon */
16163 fudge = intendedTickLength - lastTickLength;
16164 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16166 if (WhiteOnMove(forwardMostMove)) {
16167 if(whiteNPS >= 0) lastTickLength = 0;
16168 timeRemaining = whiteTimeRemaining -= lastTickLength;
16169 if(timeRemaining < 0 && !appData.icsActive) {
16170 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16171 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16172 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16173 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16176 DisplayWhiteClock(whiteTimeRemaining - fudge,
16177 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16179 if(blackNPS >= 0) lastTickLength = 0;
16180 timeRemaining = blackTimeRemaining -= lastTickLength;
16181 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16182 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16184 blackStartMove = forwardMostMove;
16185 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16188 DisplayBlackClock(blackTimeRemaining - fudge,
16189 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16191 if (CheckFlags()) return;
16194 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16195 StartClockTimer(intendedTickLength);
16197 /* if the time remaining has fallen below the alarm threshold, sound the
16198 * alarm. if the alarm has sounded and (due to a takeback or time control
16199 * with increment) the time remaining has increased to a level above the
16200 * threshold, reset the alarm so it can sound again.
16203 if (appData.icsActive && appData.icsAlarm) {
16205 /* make sure we are dealing with the user's clock */
16206 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16207 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16210 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16211 alarmSounded = FALSE;
16212 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16214 alarmSounded = TRUE;
16220 /* A player has just moved, so stop the previously running
16221 clock and (if in clock mode) start the other one.
16222 We redisplay both clocks in case we're in ICS mode, because
16223 ICS gives us an update to both clocks after every move.
16224 Note that this routine is called *after* forwardMostMove
16225 is updated, so the last fractional tick must be subtracted
16226 from the color that is *not* on move now.
16229 SwitchClocks (int newMoveNr)
16231 long lastTickLength;
16233 int flagged = FALSE;
16237 if (StopClockTimer() && appData.clockMode) {
16238 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16239 if (!WhiteOnMove(forwardMostMove)) {
16240 if(blackNPS >= 0) lastTickLength = 0;
16241 blackTimeRemaining -= lastTickLength;
16242 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16243 // if(pvInfoList[forwardMostMove].time == -1)
16244 pvInfoList[forwardMostMove].time = // use GUI time
16245 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16247 if(whiteNPS >= 0) lastTickLength = 0;
16248 whiteTimeRemaining -= lastTickLength;
16249 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16250 // if(pvInfoList[forwardMostMove].time == -1)
16251 pvInfoList[forwardMostMove].time =
16252 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16254 flagged = CheckFlags();
16256 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16257 CheckTimeControl();
16259 if (flagged || !appData.clockMode) return;
16261 switch (gameMode) {
16262 case MachinePlaysBlack:
16263 case MachinePlaysWhite:
16264 case BeginningOfGame:
16265 if (pausing) return;
16269 case PlayFromGameFile:
16277 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16278 if(WhiteOnMove(forwardMostMove))
16279 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16280 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16284 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16285 whiteTimeRemaining : blackTimeRemaining);
16286 StartClockTimer(intendedTickLength);
16290 /* Stop both clocks */
16294 long lastTickLength;
16297 if (!StopClockTimer()) return;
16298 if (!appData.clockMode) return;
16302 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16303 if (WhiteOnMove(forwardMostMove)) {
16304 if(whiteNPS >= 0) lastTickLength = 0;
16305 whiteTimeRemaining -= lastTickLength;
16306 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16308 if(blackNPS >= 0) lastTickLength = 0;
16309 blackTimeRemaining -= lastTickLength;
16310 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16315 /* Start clock of player on move. Time may have been reset, so
16316 if clock is already running, stop and restart it. */
16320 (void) StopClockTimer(); /* in case it was running already */
16321 DisplayBothClocks();
16322 if (CheckFlags()) return;
16324 if (!appData.clockMode) return;
16325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16327 GetTimeMark(&tickStartTM);
16328 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16329 whiteTimeRemaining : blackTimeRemaining);
16331 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16332 whiteNPS = blackNPS = -1;
16333 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16334 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16335 whiteNPS = first.nps;
16336 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16337 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16338 blackNPS = first.nps;
16339 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16340 whiteNPS = second.nps;
16341 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16342 blackNPS = second.nps;
16343 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16345 StartClockTimer(intendedTickLength);
16349 TimeString (long ms)
16351 long second, minute, hour, day;
16353 static char buf[32];
16355 if (ms > 0 && ms <= 9900) {
16356 /* convert milliseconds to tenths, rounding up */
16357 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16359 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16363 /* convert milliseconds to seconds, rounding up */
16364 /* use floating point to avoid strangeness of integer division
16365 with negative dividends on many machines */
16366 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16373 day = second / (60 * 60 * 24);
16374 second = second % (60 * 60 * 24);
16375 hour = second / (60 * 60);
16376 second = second % (60 * 60);
16377 minute = second / 60;
16378 second = second % 60;
16381 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16382 sign, day, hour, minute, second);
16384 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16386 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16393 * This is necessary because some C libraries aren't ANSI C compliant yet.
16396 StrStr (char *string, char *match)
16400 length = strlen(match);
16402 for (i = strlen(string) - length; i >= 0; i--, string++)
16403 if (!strncmp(match, string, length))
16410 StrCaseStr (char *string, char *match)
16414 length = strlen(match);
16416 for (i = strlen(string) - length; i >= 0; i--, string++) {
16417 for (j = 0; j < length; j++) {
16418 if (ToLower(match[j]) != ToLower(string[j]))
16421 if (j == length) return string;
16429 StrCaseCmp (char *s1, char *s2)
16434 c1 = ToLower(*s1++);
16435 c2 = ToLower(*s2++);
16436 if (c1 > c2) return 1;
16437 if (c1 < c2) return -1;
16438 if (c1 == NULLCHAR) return 0;
16446 return isupper(c) ? tolower(c) : c;
16453 return islower(c) ? toupper(c) : c;
16455 #endif /* !_amigados */
16462 if ((ret = (char *) malloc(strlen(s) + 1)))
16464 safeStrCpy(ret, s, strlen(s)+1);
16470 StrSavePtr (char *s, char **savePtr)
16475 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16476 safeStrCpy(*savePtr, s, strlen(s)+1);
16488 clock = time((time_t *)NULL);
16489 tm = localtime(&clock);
16490 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16491 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16492 return StrSave(buf);
16497 PositionToFEN (int move, char *overrideCastling)
16499 int i, j, fromX, fromY, toX, toY;
16506 whiteToPlay = (gameMode == EditPosition) ?
16507 !blackPlaysFirst : (move % 2 == 0);
16510 /* Piece placement data */
16511 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16512 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16514 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16515 if (boards[move][i][j] == EmptySquare) {
16517 } else { ChessSquare piece = boards[move][i][j];
16518 if (emptycount > 0) {
16519 if(emptycount<10) /* [HGM] can be >= 10 */
16520 *p++ = '0' + emptycount;
16521 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16524 if(PieceToChar(piece) == '+') {
16525 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16527 piece = (ChessSquare)(DEMOTED piece);
16529 *p++ = PieceToChar(piece);
16531 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16532 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16537 if (emptycount > 0) {
16538 if(emptycount<10) /* [HGM] can be >= 10 */
16539 *p++ = '0' + emptycount;
16540 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16547 /* [HGM] print Crazyhouse or Shogi holdings */
16548 if( gameInfo.holdingsWidth ) {
16549 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16551 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16552 piece = boards[move][i][BOARD_WIDTH-1];
16553 if( piece != EmptySquare )
16554 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16555 *p++ = PieceToChar(piece);
16557 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16558 piece = boards[move][BOARD_HEIGHT-i-1][0];
16559 if( piece != EmptySquare )
16560 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16561 *p++ = PieceToChar(piece);
16564 if( q == p ) *p++ = '-';
16570 *p++ = whiteToPlay ? 'w' : 'b';
16573 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16574 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16576 if(nrCastlingRights) {
16578 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16579 /* [HGM] write directly from rights */
16580 if(boards[move][CASTLING][2] != NoRights &&
16581 boards[move][CASTLING][0] != NoRights )
16582 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16583 if(boards[move][CASTLING][2] != NoRights &&
16584 boards[move][CASTLING][1] != NoRights )
16585 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16586 if(boards[move][CASTLING][5] != NoRights &&
16587 boards[move][CASTLING][3] != NoRights )
16588 *p++ = boards[move][CASTLING][3] + AAA;
16589 if(boards[move][CASTLING][5] != NoRights &&
16590 boards[move][CASTLING][4] != NoRights )
16591 *p++ = boards[move][CASTLING][4] + AAA;
16594 /* [HGM] write true castling rights */
16595 if( nrCastlingRights == 6 ) {
16596 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16597 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16598 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16599 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16600 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16601 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16602 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16603 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16606 if (q == p) *p++ = '-'; /* No castling rights */
16610 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16611 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16612 /* En passant target square */
16613 if (move > backwardMostMove) {
16614 fromX = moveList[move - 1][0] - AAA;
16615 fromY = moveList[move - 1][1] - ONE;
16616 toX = moveList[move - 1][2] - AAA;
16617 toY = moveList[move - 1][3] - ONE;
16618 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16619 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16620 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16622 /* 2-square pawn move just happened */
16624 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16628 } else if(move == backwardMostMove) {
16629 // [HGM] perhaps we should always do it like this, and forget the above?
16630 if((signed char)boards[move][EP_STATUS] >= 0) {
16631 *p++ = boards[move][EP_STATUS] + AAA;
16632 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16643 /* [HGM] find reversible plies */
16644 { int i = 0, j=move;
16646 if (appData.debugMode) { int k;
16647 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16648 for(k=backwardMostMove; k<=forwardMostMove; k++)
16649 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16653 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16654 if( j == backwardMostMove ) i += initialRulePlies;
16655 sprintf(p, "%d ", i);
16656 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16658 /* Fullmove number */
16659 sprintf(p, "%d", (move / 2) + 1);
16661 return StrSave(buf);
16665 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16674 /* [HGM] by default clear Crazyhouse holdings, if present */
16675 if(gameInfo.holdingsWidth) {
16676 for(i=0; i<BOARD_HEIGHT; i++) {
16677 board[i][0] = EmptySquare; /* black holdings */
16678 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16679 board[i][1] = (ChessSquare) 0; /* black counts */
16680 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16684 /* Piece placement data */
16685 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16688 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16689 if (*p == '/') p++;
16690 emptycount = gameInfo.boardWidth - j;
16691 while (emptycount--)
16692 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16694 #if(BOARD_FILES >= 10)
16695 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16696 p++; emptycount=10;
16697 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16698 while (emptycount--)
16699 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16701 } else if (isdigit(*p)) {
16702 emptycount = *p++ - '0';
16703 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16704 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16705 while (emptycount--)
16706 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16707 } else if (*p == '+' || isalpha(*p)) {
16708 if (j >= gameInfo.boardWidth) return FALSE;
16710 piece = CharToPiece(*++p);
16711 if(piece == EmptySquare) return FALSE; /* unknown piece */
16712 piece = (ChessSquare) (PROMOTED piece ); p++;
16713 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16714 } else piece = CharToPiece(*p++);
16716 if(piece==EmptySquare) return FALSE; /* unknown piece */
16717 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16718 piece = (ChessSquare) (PROMOTED piece);
16719 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16722 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16728 while (*p == '/' || *p == ' ') p++;
16730 /* [HGM] look for Crazyhouse holdings here */
16731 while(*p==' ') p++;
16732 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16734 if(*p == '-' ) p++; /* empty holdings */ else {
16735 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16736 /* if we would allow FEN reading to set board size, we would */
16737 /* have to add holdings and shift the board read so far here */
16738 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16740 if((int) piece >= (int) BlackPawn ) {
16741 i = (int)piece - (int)BlackPawn;
16742 i = PieceToNumber((ChessSquare)i);
16743 if( i >= gameInfo.holdingsSize ) return FALSE;
16744 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16745 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16747 i = (int)piece - (int)WhitePawn;
16748 i = PieceToNumber((ChessSquare)i);
16749 if( i >= gameInfo.holdingsSize ) return FALSE;
16750 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16751 board[i][BOARD_WIDTH-2]++; /* black holdings */
16758 while(*p == ' ') p++;
16762 if(appData.colorNickNames) {
16763 if( c == appData.colorNickNames[0] ) c = 'w'; else
16764 if( c == appData.colorNickNames[1] ) c = 'b';
16768 *blackPlaysFirst = FALSE;
16771 *blackPlaysFirst = TRUE;
16777 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16778 /* return the extra info in global variiables */
16780 /* set defaults in case FEN is incomplete */
16781 board[EP_STATUS] = EP_UNKNOWN;
16782 for(i=0; i<nrCastlingRights; i++ ) {
16783 board[CASTLING][i] =
16784 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16785 } /* assume possible unless obviously impossible */
16786 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16787 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16788 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16789 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16790 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16791 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16792 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16793 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16796 while(*p==' ') p++;
16797 if(nrCastlingRights) {
16798 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16799 /* castling indicator present, so default becomes no castlings */
16800 for(i=0; i<nrCastlingRights; i++ ) {
16801 board[CASTLING][i] = NoRights;
16804 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16805 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16806 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16807 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16808 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16810 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16811 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16812 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16814 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16815 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16816 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16817 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16818 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16819 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16822 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16823 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16824 board[CASTLING][2] = whiteKingFile;
16827 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16828 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16829 board[CASTLING][2] = whiteKingFile;
16832 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16833 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16834 board[CASTLING][5] = blackKingFile;
16837 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16838 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16839 board[CASTLING][5] = blackKingFile;
16842 default: /* FRC castlings */
16843 if(c >= 'a') { /* black rights */
16844 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16845 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16846 if(i == BOARD_RGHT) break;
16847 board[CASTLING][5] = i;
16849 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16850 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16852 board[CASTLING][3] = c;
16854 board[CASTLING][4] = c;
16855 } else { /* white rights */
16856 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16857 if(board[0][i] == WhiteKing) break;
16858 if(i == BOARD_RGHT) break;
16859 board[CASTLING][2] = i;
16860 c -= AAA - 'a' + 'A';
16861 if(board[0][c] >= WhiteKing) break;
16863 board[CASTLING][0] = c;
16865 board[CASTLING][1] = c;
16869 for(i=0; i<nrCastlingRights; i++)
16870 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16871 if (appData.debugMode) {
16872 fprintf(debugFP, "FEN castling rights:");
16873 for(i=0; i<nrCastlingRights; i++)
16874 fprintf(debugFP, " %d", board[CASTLING][i]);
16875 fprintf(debugFP, "\n");
16878 while(*p==' ') p++;
16881 /* read e.p. field in games that know e.p. capture */
16882 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16883 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16885 p++; board[EP_STATUS] = EP_NONE;
16887 char c = *p++ - AAA;
16889 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16890 if(*p >= '0' && *p <='9') p++;
16891 board[EP_STATUS] = c;
16896 if(sscanf(p, "%d", &i) == 1) {
16897 FENrulePlies = i; /* 50-move ply counter */
16898 /* (The move number is still ignored) */
16905 EditPositionPasteFEN (char *fen)
16908 Board initial_position;
16910 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16911 DisplayError(_("Bad FEN position in clipboard"), 0);
16914 int savedBlackPlaysFirst = blackPlaysFirst;
16915 EditPositionEvent();
16916 blackPlaysFirst = savedBlackPlaysFirst;
16917 CopyBoard(boards[0], initial_position);
16918 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16919 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16920 DisplayBothClocks();
16921 DrawPosition(FALSE, boards[currentMove]);
16926 static char cseq[12] = "\\ ";
16929 set_cont_sequence (char *new_seq)
16934 // handle bad attempts to set the sequence
16936 return 0; // acceptable error - no debug
16938 len = strlen(new_seq);
16939 ret = (len > 0) && (len < sizeof(cseq));
16941 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16942 else if (appData.debugMode)
16943 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16948 reformat a source message so words don't cross the width boundary. internal
16949 newlines are not removed. returns the wrapped size (no null character unless
16950 included in source message). If dest is NULL, only calculate the size required
16951 for the dest buffer. lp argument indicats line position upon entry, and it's
16952 passed back upon exit.
16955 wrap (char *dest, char *src, int count, int width, int *lp)
16957 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16959 cseq_len = strlen(cseq);
16960 old_line = line = *lp;
16961 ansi = len = clen = 0;
16963 for (i=0; i < count; i++)
16965 if (src[i] == '\033')
16968 // if we hit the width, back up
16969 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16971 // store i & len in case the word is too long
16972 old_i = i, old_len = len;
16974 // find the end of the last word
16975 while (i && src[i] != ' ' && src[i] != '\n')
16981 // word too long? restore i & len before splitting it
16982 if ((old_i-i+clen) >= width)
16989 if (i && src[i-1] == ' ')
16992 if (src[i] != ' ' && src[i] != '\n')
16999 // now append the newline and continuation sequence
17004 strncpy(dest+len, cseq, cseq_len);
17012 dest[len] = src[i];
17016 if (src[i] == '\n')
17021 if (dest && appData.debugMode)
17023 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17024 count, width, line, len, *lp);
17025 show_bytes(debugFP, src, count);
17026 fprintf(debugFP, "\ndest: ");
17027 show_bytes(debugFP, dest, len);
17028 fprintf(debugFP, "\n");
17030 *lp = dest ? line : old_line;
17035 // [HGM] vari: routines for shelving variations
17036 Boolean modeRestore = FALSE;
17039 PushInner (int firstMove, int lastMove)
17041 int i, j, nrMoves = lastMove - firstMove;
17043 // push current tail of game on stack
17044 savedResult[storedGames] = gameInfo.result;
17045 savedDetails[storedGames] = gameInfo.resultDetails;
17046 gameInfo.resultDetails = NULL;
17047 savedFirst[storedGames] = firstMove;
17048 savedLast [storedGames] = lastMove;
17049 savedFramePtr[storedGames] = framePtr;
17050 framePtr -= nrMoves; // reserve space for the boards
17051 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17052 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17053 for(j=0; j<MOVE_LEN; j++)
17054 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17055 for(j=0; j<2*MOVE_LEN; j++)
17056 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17057 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17058 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17059 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17060 pvInfoList[firstMove+i-1].depth = 0;
17061 commentList[framePtr+i] = commentList[firstMove+i];
17062 commentList[firstMove+i] = NULL;
17066 forwardMostMove = firstMove; // truncate game so we can start variation
17070 PushTail (int firstMove, int lastMove)
17072 if(appData.icsActive) { // only in local mode
17073 forwardMostMove = currentMove; // mimic old ICS behavior
17076 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17078 PushInner(firstMove, lastMove);
17079 if(storedGames == 1) GreyRevert(FALSE);
17080 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17084 PopInner (Boolean annotate)
17087 char buf[8000], moveBuf[20];
17089 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17090 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17091 nrMoves = savedLast[storedGames] - currentMove;
17094 if(!WhiteOnMove(currentMove))
17095 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17096 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17097 for(i=currentMove; i<forwardMostMove; i++) {
17099 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17100 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17101 strcat(buf, moveBuf);
17102 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17103 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17107 for(i=1; i<=nrMoves; i++) { // copy last variation back
17108 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17109 for(j=0; j<MOVE_LEN; j++)
17110 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17111 for(j=0; j<2*MOVE_LEN; j++)
17112 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17113 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17114 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17115 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17116 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17117 commentList[currentMove+i] = commentList[framePtr+i];
17118 commentList[framePtr+i] = NULL;
17120 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17121 framePtr = savedFramePtr[storedGames];
17122 gameInfo.result = savedResult[storedGames];
17123 if(gameInfo.resultDetails != NULL) {
17124 free(gameInfo.resultDetails);
17126 gameInfo.resultDetails = savedDetails[storedGames];
17127 forwardMostMove = currentMove + nrMoves;
17131 PopTail (Boolean annotate)
17133 if(appData.icsActive) return FALSE; // only in local mode
17134 if(!storedGames) return FALSE; // sanity
17135 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17137 PopInner(annotate);
17138 if(currentMove < forwardMostMove) ForwardEvent(); else
17139 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17141 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17147 { // remove all shelved variations
17149 for(i=0; i<storedGames; i++) {
17150 if(savedDetails[i])
17151 free(savedDetails[i]);
17152 savedDetails[i] = NULL;
17154 for(i=framePtr; i<MAX_MOVES; i++) {
17155 if(commentList[i]) free(commentList[i]);
17156 commentList[i] = NULL;
17158 framePtr = MAX_MOVES-1;
17163 LoadVariation (int index, char *text)
17164 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17165 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17166 int level = 0, move;
17168 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17169 // first find outermost bracketing variation
17170 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17171 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17172 if(*p == '{') wait = '}'; else
17173 if(*p == '[') wait = ']'; else
17174 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17175 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17177 if(*p == wait) wait = NULLCHAR; // closing ]} found
17180 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17181 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17182 end[1] = NULLCHAR; // clip off comment beyond variation
17183 ToNrEvent(currentMove-1);
17184 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17185 // kludge: use ParsePV() to append variation to game
17186 move = currentMove;
17187 ParsePV(start, TRUE, TRUE);
17188 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17189 ClearPremoveHighlights();
17191 ToNrEvent(currentMove+1);