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);
902 Load (ChessProgramState *cps, int i)
904 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
905 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
906 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
907 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
908 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
909 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
910 appData.firstProtocolVersion = PROTOVER;
911 ParseArgsFromString(buf);
913 ReplaceEngine(cps, i);
914 FloatToFront(&appData.recentEngineList, engineLine);
918 while(q = strchr(p, SLASH)) p = q+1;
919 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
920 if(engineDir[0] != NULLCHAR) {
921 ASSIGN(appData.directory[i], engineDir);
922 } else if(p != engineName) { // derive directory from engine path, when not given
924 ASSIGN(appData.directory[i], engineName);
926 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
927 } else { ASSIGN(appData.directory[i], "."); }
929 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
930 snprintf(command, MSG_SIZ, "%s %s", p, params);
933 ASSIGN(appData.chessProgram[i], p);
934 appData.isUCI[i] = isUCI;
935 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
936 appData.hasOwnBookUCI[i] = hasBook;
937 if(!nickName[0]) useNick = FALSE;
938 if(useNick) ASSIGN(appData.pgnName[i], nickName);
942 q = firstChessProgramNames;
943 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
944 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
945 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
946 quote, p, quote, appData.directory[i],
947 useNick ? " -fn \"" : "",
948 useNick ? nickName : "",
950 v1 ? " -firstProtocolVersion 1" : "",
951 hasBook ? "" : " -fNoOwnBookUCI",
952 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
953 storeVariant ? " -variant " : "",
954 storeVariant ? VariantName(gameInfo.variant) : "");
955 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
956 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
958 FloatToFront(&appData.recentEngineList, buf);
960 ReplaceEngine(cps, i);
966 int matched, min, sec;
968 * Parse timeControl resource
970 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
971 appData.movesPerSession)) {
973 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
974 DisplayFatalError(buf, 0, 2);
978 * Parse searchTime resource
980 if (*appData.searchTime != NULLCHAR) {
981 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
983 searchTime = min * 60;
984 } else if (matched == 2) {
985 searchTime = min * 60 + sec;
988 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
989 DisplayFatalError(buf, 0, 2);
998 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
999 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1001 GetTimeMark(&programStartTime);
1002 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1003 appData.seedBase = random() + (random()<<15);
1004 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1006 ClearProgramStats();
1007 programStats.ok_to_send = 1;
1008 programStats.seen_stat = 0;
1011 * Initialize game list
1017 * Internet chess server status
1019 if (appData.icsActive) {
1020 appData.matchMode = FALSE;
1021 appData.matchGames = 0;
1023 appData.noChessProgram = !appData.zippyPlay;
1025 appData.zippyPlay = FALSE;
1026 appData.zippyTalk = FALSE;
1027 appData.noChessProgram = TRUE;
1029 if (*appData.icsHelper != NULLCHAR) {
1030 appData.useTelnet = TRUE;
1031 appData.telnetProgram = appData.icsHelper;
1034 appData.zippyTalk = appData.zippyPlay = FALSE;
1037 /* [AS] Initialize pv info list [HGM] and game state */
1041 for( i=0; i<=framePtr; i++ ) {
1042 pvInfoList[i].depth = -1;
1043 boards[i][EP_STATUS] = EP_NONE;
1044 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1050 /* [AS] Adjudication threshold */
1051 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1053 InitEngine(&first, 0);
1054 InitEngine(&second, 1);
1057 pairing.which = "pairing"; // pairing engine
1058 pairing.pr = NoProc;
1060 pairing.program = appData.pairingEngine;
1061 pairing.host = "localhost";
1064 if (appData.icsActive) {
1065 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1066 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1067 appData.clockMode = FALSE;
1068 first.sendTime = second.sendTime = 0;
1072 /* Override some settings from environment variables, for backward
1073 compatibility. Unfortunately it's not feasible to have the env
1074 vars just set defaults, at least in xboard. Ugh.
1076 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081 if (!appData.icsActive) {
1085 /* Check for variants that are supported only in ICS mode,
1086 or not at all. Some that are accepted here nevertheless
1087 have bugs; see comments below.
1089 VariantClass variant = StringToVariant(appData.variant);
1091 case VariantBughouse: /* need four players and two boards */
1092 case VariantKriegspiel: /* need to hide pieces and move details */
1093 /* case VariantFischeRandom: (Fabien: moved below) */
1094 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1095 if( (len >= MSG_SIZ) && appData.debugMode )
1096 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1098 DisplayFatalError(buf, 0, 2);
1101 case VariantUnknown:
1102 case VariantLoadable:
1112 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1113 if( (len >= MSG_SIZ) && appData.debugMode )
1114 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1116 DisplayFatalError(buf, 0, 2);
1119 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1120 case VariantFairy: /* [HGM] TestLegality definitely off! */
1121 case VariantGothic: /* [HGM] should work */
1122 case VariantCapablanca: /* [HGM] should work */
1123 case VariantCourier: /* [HGM] initial forced moves not implemented */
1124 case VariantShogi: /* [HGM] could still mate with pawn drop */
1125 case VariantKnightmate: /* [HGM] should work */
1126 case VariantCylinder: /* [HGM] untested */
1127 case VariantFalcon: /* [HGM] untested */
1128 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1129 offboard interposition not understood */
1130 case VariantNormal: /* definitely works! */
1131 case VariantWildCastle: /* pieces not automatically shuffled */
1132 case VariantNoCastle: /* pieces not automatically shuffled */
1133 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1134 case VariantLosers: /* should work except for win condition,
1135 and doesn't know captures are mandatory */
1136 case VariantSuicide: /* should work except for win condition,
1137 and doesn't know captures are mandatory */
1138 case VariantGiveaway: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantTwoKings: /* should work */
1141 case VariantAtomic: /* should work except for win condition */
1142 case Variant3Check: /* should work except for win condition */
1143 case VariantShatranj: /* should work except for all win conditions */
1144 case VariantMakruk: /* should work except for draw countdown */
1145 case VariantBerolina: /* might work if TestLegality is off */
1146 case VariantCapaRandom: /* should work */
1147 case VariantJanus: /* should work */
1148 case VariantSuper: /* experimental */
1149 case VariantGreat: /* experimental, requires legality testing to be off */
1150 case VariantSChess: /* S-Chess, should work */
1151 case VariantGrand: /* should work */
1152 case VariantSpartan: /* should work */
1160 NextIntegerFromString (char ** str, long * value)
1165 while( *s == ' ' || *s == '\t' ) {
1171 if( *s >= '0' && *s <= '9' ) {
1172 while( *s >= '0' && *s <= '9' ) {
1173 *value = *value * 10 + (*s - '0');
1186 NextTimeControlFromString (char ** str, long * value)
1189 int result = NextIntegerFromString( str, &temp );
1192 *value = temp * 60; /* Minutes */
1193 if( **str == ':' ) {
1195 result = NextIntegerFromString( str, &temp );
1196 *value += temp; /* Seconds */
1204 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1205 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1206 int result = -1, type = 0; long temp, temp2;
1208 if(**str != ':') return -1; // old params remain in force!
1210 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1211 if( NextIntegerFromString( str, &temp ) ) return -1;
1212 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1215 /* time only: incremental or sudden-death time control */
1216 if(**str == '+') { /* increment follows; read it */
1218 if(**str == '!') type = *(*str)++; // Bronstein TC
1219 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220 *inc = temp2 * 1000;
1221 if(**str == '.') { // read fraction of increment
1222 char *start = ++(*str);
1223 if(result = NextIntegerFromString( str, &temp2)) return -1;
1225 while(start++ < *str) temp2 /= 10;
1229 *moves = 0; *tc = temp * 1000; *incType = type;
1233 (*str)++; /* classical time control */
1234 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1246 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1247 { /* [HGM] get time to add from the multi-session time-control string */
1248 int incType, moves=1; /* kludge to force reading of first session */
1249 long time, increment;
1252 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1254 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1255 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1256 if(movenr == -1) return time; /* last move before new session */
1257 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1258 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1259 if(!moves) return increment; /* current session is incremental */
1260 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1261 } while(movenr >= -1); /* try again for next session */
1263 return 0; // no new time quota on this move
1267 ParseTimeControl (char *tc, float ti, int mps)
1271 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1274 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1275 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1276 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1280 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1282 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1285 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1287 snprintf(buf, MSG_SIZ, ":%s", mytc);
1289 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1291 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296 /* Parse second time control */
1299 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1307 timeControl_2 = tc2 * 1000;
1317 timeControl = tc1 * 1000;
1320 timeIncrement = ti * 1000; /* convert to ms */
1321 movesPerSession = 0;
1324 movesPerSession = mps;
1332 if (appData.debugMode) {
1333 fprintf(debugFP, "%s\n", programVersion);
1335 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1337 set_cont_sequence(appData.wrapContSeq);
1338 if (appData.matchGames > 0) {
1339 appData.matchMode = TRUE;
1340 } else if (appData.matchMode) {
1341 appData.matchGames = 1;
1343 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1344 appData.matchGames = appData.sameColorGames;
1345 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1346 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1347 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1350 if (appData.noChessProgram || first.protocolVersion == 1) {
1353 /* kludge: allow timeout for initial "feature" commands */
1355 DisplayMessage("", _("Starting chess program"));
1356 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361 CalculateIndex (int index, int gameNr)
1362 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1364 if(index > 0) return index; // fixed nmber
1365 if(index == 0) return 1;
1366 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1367 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372 LoadGameOrPosition (int gameNr)
1373 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1374 if (*appData.loadGameFile != NULLCHAR) {
1375 if (!LoadGameFromFile(appData.loadGameFile,
1376 CalculateIndex(appData.loadGameIndex, gameNr),
1377 appData.loadGameFile, FALSE)) {
1378 DisplayFatalError(_("Bad game file"), 0, 1);
1381 } else if (*appData.loadPositionFile != NULLCHAR) {
1382 if (!LoadPositionFromFile(appData.loadPositionFile,
1383 CalculateIndex(appData.loadPositionIndex, gameNr),
1384 appData.loadPositionFile)) {
1385 DisplayFatalError(_("Bad position file"), 0, 1);
1393 ReserveGame (int gameNr, char resChar)
1395 FILE *tf = fopen(appData.tourneyFile, "r+");
1396 char *p, *q, c, buf[MSG_SIZ];
1397 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1398 safeStrCpy(buf, lastMsg, MSG_SIZ);
1399 DisplayMessage(_("Pick new game"), "");
1400 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1401 ParseArgsFromFile(tf);
1402 p = q = appData.results;
1403 if(appData.debugMode) {
1404 char *r = appData.participants;
1405 fprintf(debugFP, "results = '%s'\n", p);
1406 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1407 fprintf(debugFP, "\n");
1409 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1411 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1412 safeStrCpy(q, p, strlen(p) + 2);
1413 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1414 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1415 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1416 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1419 fseek(tf, -(strlen(p)+4), SEEK_END);
1421 if(c != '"') // depending on DOS or Unix line endings we can be one off
1422 fseek(tf, -(strlen(p)+2), SEEK_END);
1423 else fseek(tf, -(strlen(p)+3), SEEK_END);
1424 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1425 DisplayMessage(buf, "");
1426 free(p); appData.results = q;
1427 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1428 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1429 int round = appData.defaultMatchGames * appData.tourneyType;
1430 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1431 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1432 UnloadEngine(&first); // next game belongs to other pairing;
1433 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1435 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1439 MatchEvent (int mode)
1440 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1442 if(matchMode) { // already in match mode: switch it off
1444 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1447 // if(gameMode != BeginningOfGame) {
1448 // DisplayError(_("You can only start a match from the initial position."), 0);
1452 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1453 /* Set up machine vs. machine match */
1455 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1456 if(appData.tourneyFile[0]) {
1458 if(nextGame > appData.matchGames) {
1460 if(strchr(appData.results, '*') == NULL) {
1462 appData.tourneyCycles++;
1463 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1465 NextTourneyGame(-1, &dummy);
1467 if(nextGame <= appData.matchGames) {
1468 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1470 ScheduleDelayedEvent(NextMatchGame, 10000);
1475 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1476 DisplayError(buf, 0);
1477 appData.tourneyFile[0] = 0;
1481 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1482 DisplayFatalError(_("Can't have a match with no chess programs"),
1487 matchGame = roundNr = 1;
1488 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1492 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1495 InitBackEnd3 P((void))
1497 GameMode initialMode;
1501 InitChessProgram(&first, startedFromSetupPosition);
1503 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1504 free(programVersion);
1505 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1506 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1507 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1510 if (appData.icsActive) {
1512 /* [DM] Make a console window if needed [HGM] merged ifs */
1518 if (*appData.icsCommPort != NULLCHAR)
1519 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1520 appData.icsCommPort);
1522 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1523 appData.icsHost, appData.icsPort);
1525 if( (len >= MSG_SIZ) && appData.debugMode )
1526 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1528 DisplayFatalError(buf, err, 1);
1533 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1535 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1536 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1537 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1538 } else if (appData.noChessProgram) {
1544 if (*appData.cmailGameName != NULLCHAR) {
1546 OpenLoopback(&cmailPR);
1548 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1552 DisplayMessage("", "");
1553 if (StrCaseCmp(appData.initialMode, "") == 0) {
1554 initialMode = BeginningOfGame;
1555 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1556 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1557 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1558 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1561 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1562 initialMode = TwoMachinesPlay;
1563 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1564 initialMode = AnalyzeFile;
1565 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1566 initialMode = AnalyzeMode;
1567 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1568 initialMode = MachinePlaysWhite;
1569 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1570 initialMode = MachinePlaysBlack;
1571 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1572 initialMode = EditGame;
1573 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1574 initialMode = EditPosition;
1575 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1576 initialMode = Training;
1578 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1579 if( (len >= MSG_SIZ) && appData.debugMode )
1580 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1582 DisplayFatalError(buf, 0, 2);
1586 if (appData.matchMode) {
1587 if(appData.tourneyFile[0]) { // start tourney from command line
1589 if(f = fopen(appData.tourneyFile, "r")) {
1590 ParseArgsFromFile(f); // make sure tourney parmeters re known
1592 appData.clockMode = TRUE;
1594 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1597 } else if (*appData.cmailGameName != NULLCHAR) {
1598 /* Set up cmail mode */
1599 ReloadCmailMsgEvent(TRUE);
1601 /* Set up other modes */
1602 if (initialMode == AnalyzeFile) {
1603 if (*appData.loadGameFile == NULLCHAR) {
1604 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1608 if (*appData.loadGameFile != NULLCHAR) {
1609 (void) LoadGameFromFile(appData.loadGameFile,
1610 appData.loadGameIndex,
1611 appData.loadGameFile, TRUE);
1612 } else if (*appData.loadPositionFile != NULLCHAR) {
1613 (void) LoadPositionFromFile(appData.loadPositionFile,
1614 appData.loadPositionIndex,
1615 appData.loadPositionFile);
1616 /* [HGM] try to make self-starting even after FEN load */
1617 /* to allow automatic setup of fairy variants with wtm */
1618 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1619 gameMode = BeginningOfGame;
1620 setboardSpoiledMachineBlack = 1;
1622 /* [HGM] loadPos: make that every new game uses the setup */
1623 /* from file as long as we do not switch variant */
1624 if(!blackPlaysFirst) {
1625 startedFromPositionFile = TRUE;
1626 CopyBoard(filePosition, boards[0]);
1629 if (initialMode == AnalyzeMode) {
1630 if (appData.noChessProgram) {
1631 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1634 if (appData.icsActive) {
1635 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1639 } else if (initialMode == AnalyzeFile) {
1640 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1641 ShowThinkingEvent();
1643 AnalysisPeriodicEvent(1);
1644 } else if (initialMode == MachinePlaysWhite) {
1645 if (appData.noChessProgram) {
1646 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1650 if (appData.icsActive) {
1651 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1655 MachineWhiteEvent();
1656 } else if (initialMode == MachinePlaysBlack) {
1657 if (appData.noChessProgram) {
1658 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1662 if (appData.icsActive) {
1663 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1667 MachineBlackEvent();
1668 } else if (initialMode == TwoMachinesPlay) {
1669 if (appData.noChessProgram) {
1670 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1674 if (appData.icsActive) {
1675 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680 } else if (initialMode == EditGame) {
1682 } else if (initialMode == EditPosition) {
1683 EditPositionEvent();
1684 } else if (initialMode == Training) {
1685 if (*appData.loadGameFile == NULLCHAR) {
1686 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1697 DisplayBook(current+1);
1699 MoveHistorySet( movelist, first, last, current, pvInfoList );
1701 EvalGraphSet( first, last, current, pvInfoList );
1703 MakeEngineOutputTitle();
1707 * Establish will establish a contact to a remote host.port.
1708 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1709 * used to talk to the host.
1710 * Returns 0 if okay, error code if not.
1717 if (*appData.icsCommPort != NULLCHAR) {
1718 /* Talk to the host through a serial comm port */
1719 return OpenCommPort(appData.icsCommPort, &icsPR);
1721 } else if (*appData.gateway != NULLCHAR) {
1722 if (*appData.remoteShell == NULLCHAR) {
1723 /* Use the rcmd protocol to run telnet program on a gateway host */
1724 snprintf(buf, sizeof(buf), "%s %s %s",
1725 appData.telnetProgram, appData.icsHost, appData.icsPort);
1726 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1729 /* Use the rsh program to run telnet program on a gateway host */
1730 if (*appData.remoteUser == NULLCHAR) {
1731 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1732 appData.gateway, appData.telnetProgram,
1733 appData.icsHost, appData.icsPort);
1735 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1736 appData.remoteShell, appData.gateway,
1737 appData.remoteUser, appData.telnetProgram,
1738 appData.icsHost, appData.icsPort);
1740 return StartChildProcess(buf, "", &icsPR);
1743 } else if (appData.useTelnet) {
1744 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1747 /* TCP socket interface differs somewhat between
1748 Unix and NT; handle details in the front end.
1750 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755 EscapeExpand (char *p, char *q)
1756 { // [HGM] initstring: routine to shape up string arguments
1757 while(*p++ = *q++) if(p[-1] == '\\')
1759 case 'n': p[-1] = '\n'; break;
1760 case 'r': p[-1] = '\r'; break;
1761 case 't': p[-1] = '\t'; break;
1762 case '\\': p[-1] = '\\'; break;
1763 case 0: *p = 0; return;
1764 default: p[-1] = q[-1]; break;
1769 show_bytes (FILE *fp, char *buf, int count)
1772 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1773 fprintf(fp, "\\%03o", *buf & 0xff);
1782 /* Returns an errno value */
1784 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1786 char buf[8192], *p, *q, *buflim;
1787 int left, newcount, outcount;
1789 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1790 *appData.gateway != NULLCHAR) {
1791 if (appData.debugMode) {
1792 fprintf(debugFP, ">ICS: ");
1793 show_bytes(debugFP, message, count);
1794 fprintf(debugFP, "\n");
1796 return OutputToProcess(pr, message, count, outError);
1799 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1806 if (appData.debugMode) {
1807 fprintf(debugFP, ">ICS: ");
1808 show_bytes(debugFP, buf, newcount);
1809 fprintf(debugFP, "\n");
1811 outcount = OutputToProcess(pr, buf, newcount, outError);
1812 if (outcount < newcount) return -1; /* to be sure */
1819 } else if (((unsigned char) *p) == TN_IAC) {
1820 *q++ = (char) TN_IAC;
1827 if (appData.debugMode) {
1828 fprintf(debugFP, ">ICS: ");
1829 show_bytes(debugFP, buf, newcount);
1830 fprintf(debugFP, "\n");
1832 outcount = OutputToProcess(pr, buf, newcount, outError);
1833 if (outcount < newcount) return -1; /* to be sure */
1838 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1840 int outError, outCount;
1841 static int gotEof = 0;
1843 /* Pass data read from player on to ICS */
1846 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1847 if (outCount < count) {
1848 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1850 } else if (count < 0) {
1851 RemoveInputSource(isr);
1852 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1853 } else if (gotEof++ > 0) {
1854 RemoveInputSource(isr);
1855 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1861 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1862 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1863 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1864 SendToICS("date\n");
1865 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1868 /* added routine for printf style output to ics */
1870 ics_printf (char *format, ...)
1872 char buffer[MSG_SIZ];
1875 va_start(args, format);
1876 vsnprintf(buffer, sizeof(buffer), format, args);
1877 buffer[sizeof(buffer)-1] = '\0';
1885 int count, outCount, outError;
1887 if (icsPR == NoProc) return;
1890 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1891 if (outCount < count) {
1892 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896 /* This is used for sending logon scripts to the ICS. Sending
1897 without a delay causes problems when using timestamp on ICC
1898 (at least on my machine). */
1900 SendToICSDelayed (char *s, long msdelay)
1902 int count, outCount, outError;
1904 if (icsPR == NoProc) return;
1907 if (appData.debugMode) {
1908 fprintf(debugFP, ">ICS: ");
1909 show_bytes(debugFP, s, count);
1910 fprintf(debugFP, "\n");
1912 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1914 if (outCount < count) {
1915 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920 /* Remove all highlighting escape sequences in s
1921 Also deletes any suffix starting with '('
1924 StripHighlightAndTitle (char *s)
1926 static char retbuf[MSG_SIZ];
1929 while (*s != NULLCHAR) {
1930 while (*s == '\033') {
1931 while (*s != NULLCHAR && !isalpha(*s)) s++;
1932 if (*s != NULLCHAR) s++;
1934 while (*s != NULLCHAR && *s != '\033') {
1935 if (*s == '(' || *s == '[') {
1946 /* Remove all highlighting escape sequences in s */
1948 StripHighlight (char *s)
1950 static char retbuf[MSG_SIZ];
1953 while (*s != NULLCHAR) {
1954 while (*s == '\033') {
1955 while (*s != NULLCHAR && !isalpha(*s)) s++;
1956 if (*s != NULLCHAR) s++;
1958 while (*s != NULLCHAR && *s != '\033') {
1966 char *variantNames[] = VARIANT_NAMES;
1968 VariantName (VariantClass v)
1970 return variantNames[v];
1974 /* Identify a variant from the strings the chess servers use or the
1975 PGN Variant tag names we use. */
1977 StringToVariant (char *e)
1981 VariantClass v = VariantNormal;
1982 int i, found = FALSE;
1988 /* [HGM] skip over optional board-size prefixes */
1989 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1990 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1991 while( *e++ != '_');
1994 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1998 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1999 if (StrCaseStr(e, variantNames[i])) {
2000 v = (VariantClass) i;
2007 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2008 || StrCaseStr(e, "wild/fr")
2009 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2010 v = VariantFischeRandom;
2011 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2012 (i = 1, p = StrCaseStr(e, "w"))) {
2014 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2021 case 0: /* FICS only, actually */
2023 /* Castling legal even if K starts on d-file */
2024 v = VariantWildCastle;
2029 /* Castling illegal even if K & R happen to start in
2030 normal positions. */
2031 v = VariantNoCastle;
2044 /* Castling legal iff K & R start in normal positions */
2050 /* Special wilds for position setup; unclear what to do here */
2051 v = VariantLoadable;
2054 /* Bizarre ICC game */
2055 v = VariantTwoKings;
2058 v = VariantKriegspiel;
2064 v = VariantFischeRandom;
2067 v = VariantCrazyhouse;
2070 v = VariantBughouse;
2076 /* Not quite the same as FICS suicide! */
2077 v = VariantGiveaway;
2083 v = VariantShatranj;
2086 /* Temporary names for future ICC types. The name *will* change in
2087 the next xboard/WinBoard release after ICC defines it. */
2125 v = VariantCapablanca;
2128 v = VariantKnightmate;
2134 v = VariantCylinder;
2140 v = VariantCapaRandom;
2143 v = VariantBerolina;
2155 /* Found "wild" or "w" in the string but no number;
2156 must assume it's normal chess. */
2160 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2161 if( (len >= MSG_SIZ) && appData.debugMode )
2162 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2164 DisplayError(buf, 0);
2170 if (appData.debugMode) {
2171 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2172 e, wnum, VariantName(v));
2177 static int leftover_start = 0, leftover_len = 0;
2178 char star_match[STAR_MATCH_N][MSG_SIZ];
2180 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2181 advance *index beyond it, and set leftover_start to the new value of
2182 *index; else return FALSE. If pattern contains the character '*', it
2183 matches any sequence of characters not containing '\r', '\n', or the
2184 character following the '*' (if any), and the matched sequence(s) are
2185 copied into star_match.
2188 looking_at ( char *buf, int *index, char *pattern)
2190 char *bufp = &buf[*index], *patternp = pattern;
2192 char *matchp = star_match[0];
2195 if (*patternp == NULLCHAR) {
2196 *index = leftover_start = bufp - buf;
2200 if (*bufp == NULLCHAR) return FALSE;
2201 if (*patternp == '*') {
2202 if (*bufp == *(patternp + 1)) {
2204 matchp = star_match[++star_count];
2208 } else if (*bufp == '\n' || *bufp == '\r') {
2210 if (*patternp == NULLCHAR)
2215 *matchp++ = *bufp++;
2219 if (*patternp != *bufp) return FALSE;
2226 SendToPlayer (char *data, int length)
2228 int error, outCount;
2229 outCount = OutputToProcess(NoProc, data, length, &error);
2230 if (outCount < length) {
2231 DisplayFatalError(_("Error writing to display"), error, 1);
2236 PackHolding (char packed[], char *holding)
2246 switch (runlength) {
2257 sprintf(q, "%d", runlength);
2269 /* Telnet protocol requests from the front end */
2271 TelnetRequest (unsigned char ddww, unsigned char option)
2273 unsigned char msg[3];
2274 int outCount, outError;
2276 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2278 if (appData.debugMode) {
2279 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2295 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2304 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2307 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2314 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2321 if (!appData.icsActive) return;
2322 TelnetRequest(TN_DO, TN_ECHO);
2328 if (!appData.icsActive) return;
2329 TelnetRequest(TN_DONT, TN_ECHO);
2333 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2335 /* put the holdings sent to us by the server on the board holdings area */
2336 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2340 if(gameInfo.holdingsWidth < 2) return;
2341 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2342 return; // prevent overwriting by pre-board holdings
2344 if( (int)lowestPiece >= BlackPawn ) {
2347 holdingsStartRow = BOARD_HEIGHT-1;
2350 holdingsColumn = BOARD_WIDTH-1;
2351 countsColumn = BOARD_WIDTH-2;
2352 holdingsStartRow = 0;
2356 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2357 board[i][holdingsColumn] = EmptySquare;
2358 board[i][countsColumn] = (ChessSquare) 0;
2360 while( (p=*holdings++) != NULLCHAR ) {
2361 piece = CharToPiece( ToUpper(p) );
2362 if(piece == EmptySquare) continue;
2363 /*j = (int) piece - (int) WhitePawn;*/
2364 j = PieceToNumber(piece);
2365 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2366 if(j < 0) continue; /* should not happen */
2367 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2368 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2369 board[holdingsStartRow+j*direction][countsColumn]++;
2375 VariantSwitch (Board board, VariantClass newVariant)
2377 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2378 static Board oldBoard;
2380 startedFromPositionFile = FALSE;
2381 if(gameInfo.variant == newVariant) return;
2383 /* [HGM] This routine is called each time an assignment is made to
2384 * gameInfo.variant during a game, to make sure the board sizes
2385 * are set to match the new variant. If that means adding or deleting
2386 * holdings, we shift the playing board accordingly
2387 * This kludge is needed because in ICS observe mode, we get boards
2388 * of an ongoing game without knowing the variant, and learn about the
2389 * latter only later. This can be because of the move list we requested,
2390 * in which case the game history is refilled from the beginning anyway,
2391 * but also when receiving holdings of a crazyhouse game. In the latter
2392 * case we want to add those holdings to the already received position.
2396 if (appData.debugMode) {
2397 fprintf(debugFP, "Switch board from %s to %s\n",
2398 VariantName(gameInfo.variant), VariantName(newVariant));
2399 setbuf(debugFP, NULL);
2401 shuffleOpenings = 0; /* [HGM] shuffle */
2402 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2406 newWidth = 9; newHeight = 9;
2407 gameInfo.holdingsSize = 7;
2408 case VariantBughouse:
2409 case VariantCrazyhouse:
2410 newHoldingsWidth = 2; break;
2414 newHoldingsWidth = 2;
2415 gameInfo.holdingsSize = 8;
2418 case VariantCapablanca:
2419 case VariantCapaRandom:
2422 newHoldingsWidth = gameInfo.holdingsSize = 0;
2425 if(newWidth != gameInfo.boardWidth ||
2426 newHeight != gameInfo.boardHeight ||
2427 newHoldingsWidth != gameInfo.holdingsWidth ) {
2429 /* shift position to new playing area, if needed */
2430 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2431 for(i=0; i<BOARD_HEIGHT; i++)
2432 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2433 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2435 for(i=0; i<newHeight; i++) {
2436 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2437 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2439 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2440 for(i=0; i<BOARD_HEIGHT; i++)
2441 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2442 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2445 gameInfo.boardWidth = newWidth;
2446 gameInfo.boardHeight = newHeight;
2447 gameInfo.holdingsWidth = newHoldingsWidth;
2448 gameInfo.variant = newVariant;
2449 InitDrawingSizes(-2, 0);
2450 } else gameInfo.variant = newVariant;
2451 CopyBoard(oldBoard, board); // remember correctly formatted board
2452 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2453 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2456 static int loggedOn = FALSE;
2458 /*-- Game start info cache: --*/
2460 char gs_kind[MSG_SIZ];
2461 static char player1Name[128] = "";
2462 static char player2Name[128] = "";
2463 static char cont_seq[] = "\n\\ ";
2464 static int player1Rating = -1;
2465 static int player2Rating = -1;
2466 /*----------------------------*/
2468 ColorClass curColor = ColorNormal;
2469 int suppressKibitz = 0;
2472 Boolean soughtPending = FALSE;
2473 Boolean seekGraphUp;
2474 #define MAX_SEEK_ADS 200
2476 char *seekAdList[MAX_SEEK_ADS];
2477 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2478 float tcList[MAX_SEEK_ADS];
2479 char colorList[MAX_SEEK_ADS];
2480 int nrOfSeekAds = 0;
2481 int minRating = 1010, maxRating = 2800;
2482 int hMargin = 10, vMargin = 20, h, w;
2483 extern int squareSize, lineGap;
2488 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2489 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2490 if(r < minRating+100 && r >=0 ) r = minRating+100;
2491 if(r > maxRating) r = maxRating;
2492 if(tc < 1.) tc = 1.;
2493 if(tc > 95.) tc = 95.;
2494 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2495 y = ((double)r - minRating)/(maxRating - minRating)
2496 * (h-vMargin-squareSize/8-1) + vMargin;
2497 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2498 if(strstr(seekAdList[i], " u ")) color = 1;
2499 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2500 !strstr(seekAdList[i], "bullet") &&
2501 !strstr(seekAdList[i], "blitz") &&
2502 !strstr(seekAdList[i], "standard") ) color = 2;
2503 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2504 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2508 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2510 char buf[MSG_SIZ], *ext = "";
2511 VariantClass v = StringToVariant(type);
2512 if(strstr(type, "wild")) {
2513 ext = type + 4; // append wild number
2514 if(v == VariantFischeRandom) type = "chess960"; else
2515 if(v == VariantLoadable) type = "setup"; else
2516 type = VariantName(v);
2518 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2519 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2520 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2521 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2522 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2523 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2524 seekNrList[nrOfSeekAds] = nr;
2525 zList[nrOfSeekAds] = 0;
2526 seekAdList[nrOfSeekAds++] = StrSave(buf);
2527 if(plot) PlotSeekAd(nrOfSeekAds-1);
2532 EraseSeekDot (int i)
2534 int x = xList[i], y = yList[i], d=squareSize/4, k;
2535 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2536 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2537 // now replot every dot that overlapped
2538 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2539 int xx = xList[k], yy = yList[k];
2540 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2541 DrawSeekDot(xx, yy, colorList[k]);
2546 RemoveSeekAd (int nr)
2549 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2551 if(seekAdList[i]) free(seekAdList[i]);
2552 seekAdList[i] = seekAdList[--nrOfSeekAds];
2553 seekNrList[i] = seekNrList[nrOfSeekAds];
2554 ratingList[i] = ratingList[nrOfSeekAds];
2555 colorList[i] = colorList[nrOfSeekAds];
2556 tcList[i] = tcList[nrOfSeekAds];
2557 xList[i] = xList[nrOfSeekAds];
2558 yList[i] = yList[nrOfSeekAds];
2559 zList[i] = zList[nrOfSeekAds];
2560 seekAdList[nrOfSeekAds] = NULL;
2566 MatchSoughtLine (char *line)
2568 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2569 int nr, base, inc, u=0; char dummy;
2571 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2574 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2575 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2576 // match: compact and save the line
2577 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2587 if(!seekGraphUp) return FALSE;
2588 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2589 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2591 DrawSeekBackground(0, 0, w, h);
2592 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2593 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2594 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2595 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2597 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2600 snprintf(buf, MSG_SIZ, "%d", i);
2601 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2604 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2605 for(i=1; i<100; i+=(i<10?1:5)) {
2606 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2607 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2608 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2610 snprintf(buf, MSG_SIZ, "%d", i);
2611 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2614 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619 SeekGraphClick (ClickType click, int x, int y, int moving)
2621 static int lastDown = 0, displayed = 0, lastSecond;
2622 if(y < 0) return FALSE;
2623 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2624 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2625 if(!seekGraphUp) return FALSE;
2626 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2627 DrawPosition(TRUE, NULL);
2630 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2631 if(click == Release || moving) return FALSE;
2633 soughtPending = TRUE;
2634 SendToICS(ics_prefix);
2635 SendToICS("sought\n"); // should this be "sought all"?
2636 } else { // issue challenge based on clicked ad
2637 int dist = 10000; int i, closest = 0, second = 0;
2638 for(i=0; i<nrOfSeekAds; i++) {
2639 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2640 if(d < dist) { dist = d; closest = i; }
2641 second += (d - zList[i] < 120); // count in-range ads
2642 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2646 second = (second > 1);
2647 if(displayed != closest || second != lastSecond) {
2648 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2649 lastSecond = second; displayed = closest;
2651 if(click == Press) {
2652 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2655 } // on press 'hit', only show info
2656 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2657 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2658 SendToICS(ics_prefix);
2660 return TRUE; // let incoming board of started game pop down the graph
2661 } else if(click == Release) { // release 'miss' is ignored
2662 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2663 if(moving == 2) { // right up-click
2664 nrOfSeekAds = 0; // refresh graph
2665 soughtPending = TRUE;
2666 SendToICS(ics_prefix);
2667 SendToICS("sought\n"); // should this be "sought all"?
2670 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2671 // press miss or release hit 'pop down' seek graph
2672 seekGraphUp = FALSE;
2673 DrawPosition(TRUE, NULL);
2679 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2681 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2682 #define STARTED_NONE 0
2683 #define STARTED_MOVES 1
2684 #define STARTED_BOARD 2
2685 #define STARTED_OBSERVE 3
2686 #define STARTED_HOLDINGS 4
2687 #define STARTED_CHATTER 5
2688 #define STARTED_COMMENT 6
2689 #define STARTED_MOVES_NOHIDE 7
2691 static int started = STARTED_NONE;
2692 static char parse[20000];
2693 static int parse_pos = 0;
2694 static char buf[BUF_SIZE + 1];
2695 static int firstTime = TRUE, intfSet = FALSE;
2696 static ColorClass prevColor = ColorNormal;
2697 static int savingComment = FALSE;
2698 static int cmatch = 0; // continuation sequence match
2705 int backup; /* [DM] For zippy color lines */
2707 char talker[MSG_SIZ]; // [HGM] chat
2710 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2712 if (appData.debugMode) {
2714 fprintf(debugFP, "<ICS: ");
2715 show_bytes(debugFP, data, count);
2716 fprintf(debugFP, "\n");
2720 if (appData.debugMode) { int f = forwardMostMove;
2721 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2722 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2723 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2726 /* If last read ended with a partial line that we couldn't parse,
2727 prepend it to the new read and try again. */
2728 if (leftover_len > 0) {
2729 for (i=0; i<leftover_len; i++)
2730 buf[i] = buf[leftover_start + i];
2733 /* copy new characters into the buffer */
2734 bp = buf + leftover_len;
2735 buf_len=leftover_len;
2736 for (i=0; i<count; i++)
2739 if (data[i] == '\r')
2742 // join lines split by ICS?
2743 if (!appData.noJoin)
2746 Joining just consists of finding matches against the
2747 continuation sequence, and discarding that sequence
2748 if found instead of copying it. So, until a match
2749 fails, there's nothing to do since it might be the
2750 complete sequence, and thus, something we don't want
2753 if (data[i] == cont_seq[cmatch])
2756 if (cmatch == strlen(cont_seq))
2758 cmatch = 0; // complete match. just reset the counter
2761 it's possible for the ICS to not include the space
2762 at the end of the last word, making our [correct]
2763 join operation fuse two separate words. the server
2764 does this when the space occurs at the width setting.
2766 if (!buf_len || buf[buf_len-1] != ' ')
2777 match failed, so we have to copy what matched before
2778 falling through and copying this character. In reality,
2779 this will only ever be just the newline character, but
2780 it doesn't hurt to be precise.
2782 strncpy(bp, cont_seq, cmatch);
2794 buf[buf_len] = NULLCHAR;
2795 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800 while (i < buf_len) {
2801 /* Deal with part of the TELNET option negotiation
2802 protocol. We refuse to do anything beyond the
2803 defaults, except that we allow the WILL ECHO option,
2804 which ICS uses to turn off password echoing when we are
2805 directly connected to it. We reject this option
2806 if localLineEditing mode is on (always on in xboard)
2807 and we are talking to port 23, which might be a real
2808 telnet server that will try to keep WILL ECHO on permanently.
2810 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2811 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2812 unsigned char option;
2814 switch ((unsigned char) buf[++i]) {
2816 if (appData.debugMode)
2817 fprintf(debugFP, "\n<WILL ");
2818 switch (option = (unsigned char) buf[++i]) {
2820 if (appData.debugMode)
2821 fprintf(debugFP, "ECHO ");
2822 /* Reply only if this is a change, according
2823 to the protocol rules. */
2824 if (remoteEchoOption) break;
2825 if (appData.localLineEditing &&
2826 atoi(appData.icsPort) == TN_PORT) {
2827 TelnetRequest(TN_DONT, TN_ECHO);
2830 TelnetRequest(TN_DO, TN_ECHO);
2831 remoteEchoOption = TRUE;
2835 if (appData.debugMode)
2836 fprintf(debugFP, "%d ", option);
2837 /* Whatever this is, we don't want it. */
2838 TelnetRequest(TN_DONT, option);
2843 if (appData.debugMode)
2844 fprintf(debugFP, "\n<WONT ");
2845 switch (option = (unsigned char) buf[++i]) {
2847 if (appData.debugMode)
2848 fprintf(debugFP, "ECHO ");
2849 /* Reply only if this is a change, according
2850 to the protocol rules. */
2851 if (!remoteEchoOption) break;
2853 TelnetRequest(TN_DONT, TN_ECHO);
2854 remoteEchoOption = FALSE;
2857 if (appData.debugMode)
2858 fprintf(debugFP, "%d ", (unsigned char) option);
2859 /* Whatever this is, it must already be turned
2860 off, because we never agree to turn on
2861 anything non-default, so according to the
2862 protocol rules, we don't reply. */
2867 if (appData.debugMode)
2868 fprintf(debugFP, "\n<DO ");
2869 switch (option = (unsigned char) buf[++i]) {
2871 /* Whatever this is, we refuse to do it. */
2872 if (appData.debugMode)
2873 fprintf(debugFP, "%d ", option);
2874 TelnetRequest(TN_WONT, option);
2879 if (appData.debugMode)
2880 fprintf(debugFP, "\n<DONT ");
2881 switch (option = (unsigned char) buf[++i]) {
2883 if (appData.debugMode)
2884 fprintf(debugFP, "%d ", option);
2885 /* Whatever this is, we are already not doing
2886 it, because we never agree to do anything
2887 non-default, so according to the protocol
2888 rules, we don't reply. */
2893 if (appData.debugMode)
2894 fprintf(debugFP, "\n<IAC ");
2895 /* Doubled IAC; pass it through */
2899 if (appData.debugMode)
2900 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2901 /* Drop all other telnet commands on the floor */
2904 if (oldi > next_out)
2905 SendToPlayer(&buf[next_out], oldi - next_out);
2911 /* OK, this at least will *usually* work */
2912 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2916 if (loggedOn && !intfSet) {
2917 if (ics_type == ICS_ICC) {
2918 snprintf(str, MSG_SIZ,
2919 "/set-quietly interface %s\n/set-quietly style 12\n",
2921 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922 strcat(str, "/set-2 51 1\n/set seek 1\n");
2923 } else if (ics_type == ICS_CHESSNET) {
2924 snprintf(str, MSG_SIZ, "/style 12\n");
2926 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2927 strcat(str, programVersion);
2928 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2929 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2930 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2932 strcat(str, "$iset nohighlight 1\n");
2934 strcat(str, "$iset lock 1\n$style 12\n");
2937 NotifyFrontendLogin();
2941 if (started == STARTED_COMMENT) {
2942 /* Accumulate characters in comment */
2943 parse[parse_pos++] = buf[i];
2944 if (buf[i] == '\n') {
2945 parse[parse_pos] = NULLCHAR;
2946 if(chattingPartner>=0) {
2948 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2949 OutputChatMessage(chattingPartner, mess);
2950 chattingPartner = -1;
2951 next_out = i+1; // [HGM] suppress printing in ICS window
2953 if(!suppressKibitz) // [HGM] kibitz
2954 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2955 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2956 int nrDigit = 0, nrAlph = 0, j;
2957 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2958 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2959 parse[parse_pos] = NULLCHAR;
2960 // try to be smart: if it does not look like search info, it should go to
2961 // ICS interaction window after all, not to engine-output window.
2962 for(j=0; j<parse_pos; j++) { // count letters and digits
2963 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2964 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2965 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2967 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2968 int depth=0; float score;
2969 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2970 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2971 pvInfoList[forwardMostMove-1].depth = depth;
2972 pvInfoList[forwardMostMove-1].score = 100*score;
2974 OutputKibitz(suppressKibitz, parse);
2977 if(gameMode == IcsObserving) // restore original ICS messages
2978 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2980 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2981 SendToPlayer(tmp, strlen(tmp));
2983 next_out = i+1; // [HGM] suppress printing in ICS window
2985 started = STARTED_NONE;
2987 /* Don't match patterns against characters in comment */
2992 if (started == STARTED_CHATTER) {
2993 if (buf[i] != '\n') {
2994 /* Don't match patterns against characters in chatter */
2998 started = STARTED_NONE;
2999 if(suppressKibitz) next_out = i+1;
3002 /* Kludge to deal with rcmd protocol */
3003 if (firstTime && looking_at(buf, &i, "\001*")) {
3004 DisplayFatalError(&buf[1], 0, 1);
3010 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3013 if (appData.debugMode)
3014 fprintf(debugFP, "ics_type %d\n", ics_type);
3017 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3018 ics_type = ICS_FICS;
3020 if (appData.debugMode)
3021 fprintf(debugFP, "ics_type %d\n", ics_type);
3024 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3025 ics_type = ICS_CHESSNET;
3027 if (appData.debugMode)
3028 fprintf(debugFP, "ics_type %d\n", ics_type);
3033 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3034 looking_at(buf, &i, "Logging you in as \"*\"") ||
3035 looking_at(buf, &i, "will be \"*\""))) {
3036 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3040 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3042 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3043 DisplayIcsInteractionTitle(buf);
3044 have_set_title = TRUE;
3047 /* skip finger notes */
3048 if (started == STARTED_NONE &&
3049 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3050 (buf[i] == '1' && buf[i+1] == '0')) &&
3051 buf[i+2] == ':' && buf[i+3] == ' ') {
3052 started = STARTED_CHATTER;
3058 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3059 if(appData.seekGraph) {
3060 if(soughtPending && MatchSoughtLine(buf+i)) {
3061 i = strstr(buf+i, "rated") - buf;
3062 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063 next_out = leftover_start = i;
3064 started = STARTED_CHATTER;
3065 suppressKibitz = TRUE;
3068 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3069 && looking_at(buf, &i, "* ads displayed")) {
3070 soughtPending = FALSE;
3075 if(appData.autoRefresh) {
3076 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3077 int s = (ics_type == ICS_ICC); // ICC format differs
3079 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3080 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3081 looking_at(buf, &i, "*% "); // eat prompt
3082 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3083 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084 next_out = i; // suppress
3087 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3088 char *p = star_match[0];
3090 if(seekGraphUp) RemoveSeekAd(atoi(p));
3091 while(*p && *p++ != ' '); // next
3093 looking_at(buf, &i, "*% "); // eat prompt
3094 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101 /* skip formula vars */
3102 if (started == STARTED_NONE &&
3103 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3104 started = STARTED_CHATTER;
3109 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3110 if (appData.autoKibitz && started == STARTED_NONE &&
3111 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3112 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3113 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3114 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3115 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3116 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3117 suppressKibitz = TRUE;
3118 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3121 && (gameMode == IcsPlayingWhite)) ||
3122 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3123 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3124 started = STARTED_CHATTER; // own kibitz we simply discard
3126 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3127 parse_pos = 0; parse[0] = NULLCHAR;
3128 savingComment = TRUE;
3129 suppressKibitz = gameMode != IcsObserving ? 2 :
3130 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3134 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3135 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3136 && atoi(star_match[0])) {
3137 // suppress the acknowledgements of our own autoKibitz
3139 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3141 SendToPlayer(star_match[0], strlen(star_match[0]));
3142 if(looking_at(buf, &i, "*% ")) // eat prompt
3143 suppressKibitz = FALSE;
3147 } // [HGM] kibitz: end of patch
3149 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3151 // [HGM] chat: intercept tells by users for which we have an open chat window
3153 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3154 looking_at(buf, &i, "* whispers:") ||
3155 looking_at(buf, &i, "* kibitzes:") ||
3156 looking_at(buf, &i, "* shouts:") ||
3157 looking_at(buf, &i, "* c-shouts:") ||
3158 looking_at(buf, &i, "--> * ") ||
3159 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3160 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3161 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3162 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3164 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3165 chattingPartner = -1;
3167 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3168 for(p=0; p<MAX_CHAT; p++) {
3169 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3170 talker[0] = '['; strcat(talker, "] ");
3171 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3172 chattingPartner = p; break;
3175 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3176 for(p=0; p<MAX_CHAT; p++) {
3177 if(!strcmp("kibitzes", chatPartner[p])) {
3178 talker[0] = '['; strcat(talker, "] ");
3179 chattingPartner = p; break;
3182 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3183 for(p=0; p<MAX_CHAT; p++) {
3184 if(!strcmp("whispers", chatPartner[p])) {
3185 talker[0] = '['; strcat(talker, "] ");
3186 chattingPartner = p; break;
3189 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3190 if(buf[i-8] == '-' && buf[i-3] == 't')
3191 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3192 if(!strcmp("c-shouts", chatPartner[p])) {
3193 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3194 chattingPartner = p; break;
3197 if(chattingPartner < 0)
3198 for(p=0; p<MAX_CHAT; p++) {
3199 if(!strcmp("shouts", chatPartner[p])) {
3200 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3201 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3202 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3203 chattingPartner = p; break;
3207 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3208 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3209 talker[0] = 0; Colorize(ColorTell, FALSE);
3210 chattingPartner = p; break;
3212 if(chattingPartner<0) i = oldi; else {
3213 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3214 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3215 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3216 started = STARTED_COMMENT;
3217 parse_pos = 0; parse[0] = NULLCHAR;
3218 savingComment = 3 + chattingPartner; // counts as TRUE
3219 suppressKibitz = TRUE;
3222 } // [HGM] chat: end of patch
3225 if (appData.zippyTalk || appData.zippyPlay) {
3226 /* [DM] Backup address for color zippy lines */
3228 if (loggedOn == TRUE)
3229 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3230 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3232 } // [DM] 'else { ' deleted
3234 /* Regular tells and says */
3235 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3236 looking_at(buf, &i, "* (your partner) tells you: ") ||
3237 looking_at(buf, &i, "* says: ") ||
3238 /* Don't color "message" or "messages" output */
3239 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3240 looking_at(buf, &i, "*. * at *:*: ") ||
3241 looking_at(buf, &i, "--* (*:*): ") ||
3242 /* Message notifications (same color as tells) */
3243 looking_at(buf, &i, "* has left a message ") ||
3244 looking_at(buf, &i, "* just sent you a message:\n") ||
3245 /* Whispers and kibitzes */
3246 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3247 looking_at(buf, &i, "* kibitzes: ") ||
3249 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3251 if (tkind == 1 && strchr(star_match[0], ':')) {
3252 /* Avoid "tells you:" spoofs in channels */
3255 if (star_match[0][0] == NULLCHAR ||
3256 strchr(star_match[0], ' ') ||
3257 (tkind == 3 && strchr(star_match[1], ' '))) {
3258 /* Reject bogus matches */
3261 if (appData.colorize) {
3262 if (oldi > next_out) {
3263 SendToPlayer(&buf[next_out], oldi - next_out);
3268 Colorize(ColorTell, FALSE);
3269 curColor = ColorTell;
3272 Colorize(ColorKibitz, FALSE);
3273 curColor = ColorKibitz;
3276 p = strrchr(star_match[1], '(');
3283 Colorize(ColorChannel1, FALSE);
3284 curColor = ColorChannel1;
3286 Colorize(ColorChannel, FALSE);
3287 curColor = ColorChannel;
3291 curColor = ColorNormal;
3295 if (started == STARTED_NONE && appData.autoComment &&
3296 (gameMode == IcsObserving ||
3297 gameMode == IcsPlayingWhite ||
3298 gameMode == IcsPlayingBlack)) {
3299 parse_pos = i - oldi;
3300 memcpy(parse, &buf[oldi], parse_pos);
3301 parse[parse_pos] = NULLCHAR;
3302 started = STARTED_COMMENT;
3303 savingComment = TRUE;
3305 started = STARTED_CHATTER;
3306 savingComment = FALSE;
3313 if (looking_at(buf, &i, "* s-shouts: ") ||
3314 looking_at(buf, &i, "* c-shouts: ")) {
3315 if (appData.colorize) {
3316 if (oldi > next_out) {
3317 SendToPlayer(&buf[next_out], oldi - next_out);
3320 Colorize(ColorSShout, FALSE);
3321 curColor = ColorSShout;
3324 started = STARTED_CHATTER;
3328 if (looking_at(buf, &i, "--->")) {
3333 if (looking_at(buf, &i, "* shouts: ") ||
3334 looking_at(buf, &i, "--> ")) {
3335 if (appData.colorize) {
3336 if (oldi > next_out) {
3337 SendToPlayer(&buf[next_out], oldi - next_out);
3340 Colorize(ColorShout, FALSE);
3341 curColor = ColorShout;
3344 started = STARTED_CHATTER;
3348 if (looking_at( buf, &i, "Challenge:")) {
3349 if (appData.colorize) {
3350 if (oldi > next_out) {
3351 SendToPlayer(&buf[next_out], oldi - next_out);
3354 Colorize(ColorChallenge, FALSE);
3355 curColor = ColorChallenge;
3361 if (looking_at(buf, &i, "* offers you") ||
3362 looking_at(buf, &i, "* offers to be") ||
3363 looking_at(buf, &i, "* would like to") ||
3364 looking_at(buf, &i, "* requests to") ||
3365 looking_at(buf, &i, "Your opponent offers") ||
3366 looking_at(buf, &i, "Your opponent requests")) {
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(ColorRequest, FALSE);
3374 curColor = ColorRequest;
3379 if (looking_at(buf, &i, "* (*) seeking")) {
3380 if (appData.colorize) {
3381 if (oldi > next_out) {
3382 SendToPlayer(&buf[next_out], oldi - next_out);
3385 Colorize(ColorSeek, FALSE);
3386 curColor = ColorSeek;
3391 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3393 if (looking_at(buf, &i, "\\ ")) {
3394 if (prevColor != ColorNormal) {
3395 if (oldi > next_out) {
3396 SendToPlayer(&buf[next_out], oldi - next_out);
3399 Colorize(prevColor, TRUE);
3400 curColor = prevColor;
3402 if (savingComment) {
3403 parse_pos = i - oldi;
3404 memcpy(parse, &buf[oldi], parse_pos);
3405 parse[parse_pos] = NULLCHAR;
3406 started = STARTED_COMMENT;
3407 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3408 chattingPartner = savingComment - 3; // kludge to remember the box
3410 started = STARTED_CHATTER;
3415 if (looking_at(buf, &i, "Black Strength :") ||
3416 looking_at(buf, &i, "<<< style 10 board >>>") ||
3417 looking_at(buf, &i, "<10>") ||
3418 looking_at(buf, &i, "#@#")) {
3419 /* Wrong board style */
3421 SendToICS(ics_prefix);
3422 SendToICS("set style 12\n");
3423 SendToICS(ics_prefix);
3424 SendToICS("refresh\n");
3428 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3430 have_sent_ICS_logon = 1;
3434 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3435 (looking_at(buf, &i, "\n<12> ") ||
3436 looking_at(buf, &i, "<12> "))) {
3438 if (oldi > next_out) {
3439 SendToPlayer(&buf[next_out], oldi - next_out);
3442 started = STARTED_BOARD;
3447 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3448 looking_at(buf, &i, "<b1> ")) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 started = STARTED_HOLDINGS;
3458 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3460 /* Header for a move list -- first line */
3462 switch (ics_getting_history) {
3466 case BeginningOfGame:
3467 /* User typed "moves" or "oldmoves" while we
3468 were idle. Pretend we asked for these
3469 moves and soak them up so user can step
3470 through them and/or save them.
3473 gameMode = IcsObserving;
3476 ics_getting_history = H_GOT_UNREQ_HEADER;
3478 case EditGame: /*?*/
3479 case EditPosition: /*?*/
3480 /* Should above feature work in these modes too? */
3481 /* For now it doesn't */
3482 ics_getting_history = H_GOT_UNWANTED_HEADER;
3485 ics_getting_history = H_GOT_UNWANTED_HEADER;
3490 /* Is this the right one? */
3491 if (gameInfo.white && gameInfo.black &&
3492 strcmp(gameInfo.white, star_match[0]) == 0 &&
3493 strcmp(gameInfo.black, star_match[2]) == 0) {
3495 ics_getting_history = H_GOT_REQ_HEADER;
3498 case H_GOT_REQ_HEADER:
3499 case H_GOT_UNREQ_HEADER:
3500 case H_GOT_UNWANTED_HEADER:
3501 case H_GETTING_MOVES:
3502 /* Should not happen */
3503 DisplayError(_("Error gathering move list: two headers"), 0);
3504 ics_getting_history = H_FALSE;
3508 /* Save player ratings into gameInfo if needed */
3509 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3510 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3511 (gameInfo.whiteRating == -1 ||
3512 gameInfo.blackRating == -1)) {
3514 gameInfo.whiteRating = string_to_rating(star_match[1]);
3515 gameInfo.blackRating = string_to_rating(star_match[3]);
3516 if (appData.debugMode)
3517 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3518 gameInfo.whiteRating, gameInfo.blackRating);
3523 if (looking_at(buf, &i,
3524 "* * match, initial time: * minute*, increment: * second")) {
3525 /* Header for a move list -- second line */
3526 /* Initial board will follow if this is a wild game */
3527 if (gameInfo.event != NULL) free(gameInfo.event);
3528 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3529 gameInfo.event = StrSave(str);
3530 /* [HGM] we switched variant. Translate boards if needed. */
3531 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3535 if (looking_at(buf, &i, "Move ")) {
3536 /* Beginning of a move list */
3537 switch (ics_getting_history) {
3539 /* Normally should not happen */
3540 /* Maybe user hit reset while we were parsing */
3543 /* Happens if we are ignoring a move list that is not
3544 * the one we just requested. Common if the user
3545 * tries to observe two games without turning off
3548 case H_GETTING_MOVES:
3549 /* Should not happen */
3550 DisplayError(_("Error gathering move list: nested"), 0);
3551 ics_getting_history = H_FALSE;
3553 case H_GOT_REQ_HEADER:
3554 ics_getting_history = H_GETTING_MOVES;
3555 started = STARTED_MOVES;
3557 if (oldi > next_out) {
3558 SendToPlayer(&buf[next_out], oldi - next_out);
3561 case H_GOT_UNREQ_HEADER:
3562 ics_getting_history = H_GETTING_MOVES;
3563 started = STARTED_MOVES_NOHIDE;
3566 case H_GOT_UNWANTED_HEADER:
3567 ics_getting_history = H_FALSE;
3573 if (looking_at(buf, &i, "% ") ||
3574 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3575 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3576 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3577 soughtPending = FALSE;
3581 if(suppressKibitz) next_out = i;
3582 savingComment = FALSE;
3586 case STARTED_MOVES_NOHIDE:
3587 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3588 parse[parse_pos + i - oldi] = NULLCHAR;
3589 ParseGameHistory(parse);
3591 if (appData.zippyPlay && first.initDone) {
3592 FeedMovesToProgram(&first, forwardMostMove);
3593 if (gameMode == IcsPlayingWhite) {
3594 if (WhiteOnMove(forwardMostMove)) {
3595 if (first.sendTime) {
3596 if (first.useColors) {
3597 SendToProgram("black\n", &first);
3599 SendTimeRemaining(&first, TRUE);
3601 if (first.useColors) {
3602 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3604 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3605 first.maybeThinking = TRUE;
3607 if (first.usePlayother) {
3608 if (first.sendTime) {
3609 SendTimeRemaining(&first, TRUE);
3611 SendToProgram("playother\n", &first);
3617 } else if (gameMode == IcsPlayingBlack) {
3618 if (!WhiteOnMove(forwardMostMove)) {
3619 if (first.sendTime) {
3620 if (first.useColors) {
3621 SendToProgram("white\n", &first);
3623 SendTimeRemaining(&first, FALSE);
3625 if (first.useColors) {
3626 SendToProgram("black\n", &first);
3628 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3629 first.maybeThinking = TRUE;
3631 if (first.usePlayother) {
3632 if (first.sendTime) {
3633 SendTimeRemaining(&first, FALSE);
3635 SendToProgram("playother\n", &first);
3644 if (gameMode == IcsObserving && ics_gamenum == -1) {
3645 /* Moves came from oldmoves or moves command
3646 while we weren't doing anything else.
3648 currentMove = forwardMostMove;
3649 ClearHighlights();/*!!could figure this out*/
3650 flipView = appData.flipView;
3651 DrawPosition(TRUE, boards[currentMove]);
3652 DisplayBothClocks();
3653 snprintf(str, MSG_SIZ, "%s %s %s",
3654 gameInfo.white, _("vs."), gameInfo.black);
3658 /* Moves were history of an active game */
3659 if (gameInfo.resultDetails != NULL) {
3660 free(gameInfo.resultDetails);
3661 gameInfo.resultDetails = NULL;
3664 HistorySet(parseList, backwardMostMove,
3665 forwardMostMove, currentMove-1);
3666 DisplayMove(currentMove - 1);
3667 if (started == STARTED_MOVES) next_out = i;
3668 started = STARTED_NONE;
3669 ics_getting_history = H_FALSE;
3672 case STARTED_OBSERVE:
3673 started = STARTED_NONE;
3674 SendToICS(ics_prefix);
3675 SendToICS("refresh\n");
3681 if(bookHit) { // [HGM] book: simulate book reply
3682 static char bookMove[MSG_SIZ]; // a bit generous?
3684 programStats.nodes = programStats.depth = programStats.time =
3685 programStats.score = programStats.got_only_move = 0;
3686 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3688 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3689 strcat(bookMove, bookHit);
3690 HandleMachineMove(bookMove, &first);
3695 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3696 started == STARTED_HOLDINGS ||
3697 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3698 /* Accumulate characters in move list or board */
3699 parse[parse_pos++] = buf[i];
3702 /* Start of game messages. Mostly we detect start of game
3703 when the first board image arrives. On some versions
3704 of the ICS, though, we need to do a "refresh" after starting
3705 to observe in order to get the current board right away. */
3706 if (looking_at(buf, &i, "Adding game * to observation list")) {
3707 started = STARTED_OBSERVE;
3711 /* Handle auto-observe */
3712 if (appData.autoObserve &&
3713 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3714 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3716 /* Choose the player that was highlighted, if any. */
3717 if (star_match[0][0] == '\033' ||
3718 star_match[1][0] != '\033') {
3719 player = star_match[0];
3721 player = star_match[2];
3723 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3724 ics_prefix, StripHighlightAndTitle(player));
3727 /* Save ratings from notify string */
3728 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3729 player1Rating = string_to_rating(star_match[1]);
3730 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3731 player2Rating = string_to_rating(star_match[3]);
3733 if (appData.debugMode)
3735 "Ratings from 'Game notification:' %s %d, %s %d\n",
3736 player1Name, player1Rating,
3737 player2Name, player2Rating);
3742 /* Deal with automatic examine mode after a game,
3743 and with IcsObserving -> IcsExamining transition */
3744 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3745 looking_at(buf, &i, "has made you an examiner of game *")) {
3747 int gamenum = atoi(star_match[0]);
3748 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3749 gamenum == ics_gamenum) {
3750 /* We were already playing or observing this game;
3751 no need to refetch history */
3752 gameMode = IcsExamining;
3754 pauseExamForwardMostMove = forwardMostMove;
3755 } else if (currentMove < forwardMostMove) {
3756 ForwardInner(forwardMostMove);
3759 /* I don't think this case really can happen */
3760 SendToICS(ics_prefix);
3761 SendToICS("refresh\n");
3766 /* Error messages */
3767 // if (ics_user_moved) {
3768 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3769 if (looking_at(buf, &i, "Illegal move") ||
3770 looking_at(buf, &i, "Not a legal move") ||
3771 looking_at(buf, &i, "Your king is in check") ||
3772 looking_at(buf, &i, "It isn't your turn") ||
3773 looking_at(buf, &i, "It is not your move")) {
3775 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3776 currentMove = forwardMostMove-1;
3777 DisplayMove(currentMove - 1); /* before DMError */
3778 DrawPosition(FALSE, boards[currentMove]);
3779 SwitchClocks(forwardMostMove-1); // [HGM] race
3780 DisplayBothClocks();
3782 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788 if (looking_at(buf, &i, "still have time") ||
3789 looking_at(buf, &i, "not out of time") ||
3790 looking_at(buf, &i, "either player is out of time") ||
3791 looking_at(buf, &i, "has timeseal; checking")) {
3792 /* We must have called his flag a little too soon */
3793 whiteFlag = blackFlag = FALSE;
3797 if (looking_at(buf, &i, "added * seconds to") ||
3798 looking_at(buf, &i, "seconds were added to")) {
3799 /* Update the clocks */
3800 SendToICS(ics_prefix);
3801 SendToICS("refresh\n");
3805 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3806 ics_clock_paused = TRUE;
3811 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3812 ics_clock_paused = FALSE;
3817 /* Grab player ratings from the Creating: message.
3818 Note we have to check for the special case when
3819 the ICS inserts things like [white] or [black]. */
3820 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3821 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3823 0 player 1 name (not necessarily white)
3825 2 empty, white, or black (IGNORED)
3826 3 player 2 name (not necessarily black)
3829 The names/ratings are sorted out when the game
3830 actually starts (below).
3832 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3833 player1Rating = string_to_rating(star_match[1]);
3834 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3835 player2Rating = string_to_rating(star_match[4]);
3837 if (appData.debugMode)
3839 "Ratings from 'Creating:' %s %d, %s %d\n",
3840 player1Name, player1Rating,
3841 player2Name, player2Rating);
3846 /* Improved generic start/end-of-game messages */
3847 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3848 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3849 /* If tkind == 0: */
3850 /* star_match[0] is the game number */
3851 /* [1] is the white player's name */
3852 /* [2] is the black player's name */
3853 /* For end-of-game: */
3854 /* [3] is the reason for the game end */
3855 /* [4] is a PGN end game-token, preceded by " " */
3856 /* For start-of-game: */
3857 /* [3] begins with "Creating" or "Continuing" */
3858 /* [4] is " *" or empty (don't care). */
3859 int gamenum = atoi(star_match[0]);
3860 char *whitename, *blackname, *why, *endtoken;
3861 ChessMove endtype = EndOfFile;
3864 whitename = star_match[1];
3865 blackname = star_match[2];
3866 why = star_match[3];
3867 endtoken = star_match[4];
3869 whitename = star_match[1];
3870 blackname = star_match[3];
3871 why = star_match[5];
3872 endtoken = star_match[6];
3875 /* Game start messages */
3876 if (strncmp(why, "Creating ", 9) == 0 ||
3877 strncmp(why, "Continuing ", 11) == 0) {
3878 gs_gamenum = gamenum;
3879 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3880 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3881 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3883 if (appData.zippyPlay) {
3884 ZippyGameStart(whitename, blackname);
3887 partnerBoardValid = FALSE; // [HGM] bughouse
3891 /* Game end messages */
3892 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3893 ics_gamenum != gamenum) {
3896 while (endtoken[0] == ' ') endtoken++;
3897 switch (endtoken[0]) {
3900 endtype = GameUnfinished;
3903 endtype = BlackWins;
3906 if (endtoken[1] == '/')
3907 endtype = GameIsDrawn;
3909 endtype = WhiteWins;
3912 GameEnds(endtype, why, GE_ICS);
3914 if (appData.zippyPlay && first.initDone) {
3915 ZippyGameEnd(endtype, why);
3916 if (first.pr == NoProc) {
3917 /* Start the next process early so that we'll
3918 be ready for the next challenge */
3919 StartChessProgram(&first);
3921 /* Send "new" early, in case this command takes
3922 a long time to finish, so that we'll be ready
3923 for the next challenge. */
3924 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3928 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3932 if (looking_at(buf, &i, "Removing game * from observation") ||
3933 looking_at(buf, &i, "no longer observing game *") ||
3934 looking_at(buf, &i, "Game * (*) has no examiners")) {
3935 if (gameMode == IcsObserving &&
3936 atoi(star_match[0]) == ics_gamenum)
3938 /* icsEngineAnalyze */
3939 if (appData.icsEngineAnalyze) {
3946 ics_user_moved = FALSE;
3951 if (looking_at(buf, &i, "no longer examining game *")) {
3952 if (gameMode == IcsExamining &&
3953 atoi(star_match[0]) == ics_gamenum)
3957 ics_user_moved = FALSE;
3962 /* Advance leftover_start past any newlines we find,
3963 so only partial lines can get reparsed */
3964 if (looking_at(buf, &i, "\n")) {
3965 prevColor = curColor;
3966 if (curColor != ColorNormal) {
3967 if (oldi > next_out) {
3968 SendToPlayer(&buf[next_out], oldi - next_out);
3971 Colorize(ColorNormal, FALSE);
3972 curColor = ColorNormal;
3974 if (started == STARTED_BOARD) {
3975 started = STARTED_NONE;
3976 parse[parse_pos] = NULLCHAR;
3977 ParseBoard12(parse);
3980 /* Send premove here */
3981 if (appData.premove) {
3983 if (currentMove == 0 &&
3984 gameMode == IcsPlayingWhite &&
3985 appData.premoveWhite) {
3986 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3987 if (appData.debugMode)
3988 fprintf(debugFP, "Sending premove:\n");
3990 } else if (currentMove == 1 &&
3991 gameMode == IcsPlayingBlack &&
3992 appData.premoveBlack) {
3993 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3994 if (appData.debugMode)
3995 fprintf(debugFP, "Sending premove:\n");
3997 } else if (gotPremove) {
3999 ClearPremoveHighlights();
4000 if (appData.debugMode)
4001 fprintf(debugFP, "Sending premove:\n");
4002 UserMoveEvent(premoveFromX, premoveFromY,
4003 premoveToX, premoveToY,
4008 /* Usually suppress following prompt */
4009 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4010 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4011 if (looking_at(buf, &i, "*% ")) {
4012 savingComment = FALSE;
4017 } else if (started == STARTED_HOLDINGS) {
4019 char new_piece[MSG_SIZ];
4020 started = STARTED_NONE;
4021 parse[parse_pos] = NULLCHAR;
4022 if (appData.debugMode)
4023 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4024 parse, currentMove);
4025 if (sscanf(parse, " game %d", &gamenum) == 1) {
4026 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4027 if (gameInfo.variant == VariantNormal) {
4028 /* [HGM] We seem to switch variant during a game!
4029 * Presumably no holdings were displayed, so we have
4030 * to move the position two files to the right to
4031 * create room for them!
4033 VariantClass newVariant;
4034 switch(gameInfo.boardWidth) { // base guess on board width
4035 case 9: newVariant = VariantShogi; break;
4036 case 10: newVariant = VariantGreat; break;
4037 default: newVariant = VariantCrazyhouse; break;
4039 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4040 /* Get a move list just to see the header, which
4041 will tell us whether this is really bug or zh */
4042 if (ics_getting_history == H_FALSE) {
4043 ics_getting_history = H_REQUESTED;
4044 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4048 new_piece[0] = NULLCHAR;
4049 sscanf(parse, "game %d white [%s black [%s <- %s",
4050 &gamenum, white_holding, black_holding,
4052 white_holding[strlen(white_holding)-1] = NULLCHAR;
4053 black_holding[strlen(black_holding)-1] = NULLCHAR;
4054 /* [HGM] copy holdings to board holdings area */
4055 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4056 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4057 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4059 if (appData.zippyPlay && first.initDone) {
4060 ZippyHoldings(white_holding, black_holding,
4064 if (tinyLayout || smallLayout) {
4065 char wh[16], bh[16];
4066 PackHolding(wh, white_holding);
4067 PackHolding(bh, black_holding);
4068 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4069 gameInfo.white, gameInfo.black);
4071 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4072 gameInfo.white, white_holding, _("vs."),
4073 gameInfo.black, black_holding);
4075 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4076 DrawPosition(FALSE, boards[currentMove]);
4078 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4079 sscanf(parse, "game %d white [%s black [%s <- %s",
4080 &gamenum, white_holding, black_holding,
4082 white_holding[strlen(white_holding)-1] = NULLCHAR;
4083 black_holding[strlen(black_holding)-1] = NULLCHAR;
4084 /* [HGM] copy holdings to partner-board holdings area */
4085 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4086 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4087 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4088 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4089 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4092 /* Suppress following prompt */
4093 if (looking_at(buf, &i, "*% ")) {
4094 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4095 savingComment = FALSE;
4103 i++; /* skip unparsed character and loop back */
4106 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4107 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4108 // SendToPlayer(&buf[next_out], i - next_out);
4109 started != STARTED_HOLDINGS && leftover_start > next_out) {
4110 SendToPlayer(&buf[next_out], leftover_start - next_out);
4114 leftover_len = buf_len - leftover_start;
4115 /* if buffer ends with something we couldn't parse,
4116 reparse it after appending the next read */
4118 } else if (count == 0) {
4119 RemoveInputSource(isr);
4120 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4122 DisplayFatalError(_("Error reading from ICS"), error, 1);
4127 /* Board style 12 looks like this:
4129 <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
4131 * The "<12> " is stripped before it gets to this routine. The two
4132 * trailing 0's (flip state and clock ticking) are later addition, and
4133 * some chess servers may not have them, or may have only the first.
4134 * Additional trailing fields may be added in the future.
4137 #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"
4139 #define RELATION_OBSERVING_PLAYED 0
4140 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4141 #define RELATION_PLAYING_MYMOVE 1
4142 #define RELATION_PLAYING_NOTMYMOVE -1
4143 #define RELATION_EXAMINING 2
4144 #define RELATION_ISOLATED_BOARD -3
4145 #define RELATION_STARTING_POSITION -4 /* FICS only */
4148 ParseBoard12 (char *string)
4150 GameMode newGameMode;
4151 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4152 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4153 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4154 char to_play, board_chars[200];
4155 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4156 char black[32], white[32];
4158 int prevMove = currentMove;
4161 int fromX, fromY, toX, toY;
4163 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4164 char *bookHit = NULL; // [HGM] book
4165 Boolean weird = FALSE, reqFlag = FALSE;
4167 fromX = fromY = toX = toY = -1;
4171 if (appData.debugMode)
4172 fprintf(debugFP, _("Parsing board: %s\n"), string);
4174 move_str[0] = NULLCHAR;
4175 elapsed_time[0] = NULLCHAR;
4176 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4178 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4179 if(string[i] == ' ') { ranks++; files = 0; }
4181 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4184 for(j = 0; j <i; j++) board_chars[j] = string[j];
4185 board_chars[i] = '\0';
4188 n = sscanf(string, PATTERN, &to_play, &double_push,
4189 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4190 &gamenum, white, black, &relation, &basetime, &increment,
4191 &white_stren, &black_stren, &white_time, &black_time,
4192 &moveNum, str, elapsed_time, move_str, &ics_flip,
4196 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4197 DisplayError(str, 0);
4201 /* Convert the move number to internal form */
4202 moveNum = (moveNum - 1) * 2;
4203 if (to_play == 'B') moveNum++;
4204 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4205 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211 case RELATION_OBSERVING_PLAYED:
4212 case RELATION_OBSERVING_STATIC:
4213 if (gamenum == -1) {
4214 /* Old ICC buglet */
4215 relation = RELATION_OBSERVING_STATIC;
4217 newGameMode = IcsObserving;
4219 case RELATION_PLAYING_MYMOVE:
4220 case RELATION_PLAYING_NOTMYMOVE:
4222 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4223 IcsPlayingWhite : IcsPlayingBlack;
4224 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4226 case RELATION_EXAMINING:
4227 newGameMode = IcsExamining;
4229 case RELATION_ISOLATED_BOARD:
4231 /* Just display this board. If user was doing something else,
4232 we will forget about it until the next board comes. */
4233 newGameMode = IcsIdle;
4235 case RELATION_STARTING_POSITION:
4236 newGameMode = gameMode;
4240 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4241 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4242 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4244 for (k = 0; k < ranks; k++) {
4245 for (j = 0; j < files; j++)
4246 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4247 if(gameInfo.holdingsWidth > 1) {
4248 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4249 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4252 CopyBoard(partnerBoard, board);
4253 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4254 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4255 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4256 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4257 if(toSqr = strchr(str, '-')) {
4258 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4259 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4260 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4261 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4262 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4263 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4264 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4265 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4266 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4267 DisplayMessage(partnerStatus, "");
4268 partnerBoardValid = TRUE;
4272 /* Modify behavior for initial board display on move listing
4275 switch (ics_getting_history) {
4279 case H_GOT_REQ_HEADER:
4280 case H_GOT_UNREQ_HEADER:
4281 /* This is the initial position of the current game */
4282 gamenum = ics_gamenum;
4283 moveNum = 0; /* old ICS bug workaround */
4284 if (to_play == 'B') {
4285 startedFromSetupPosition = TRUE;
4286 blackPlaysFirst = TRUE;
4288 if (forwardMostMove == 0) forwardMostMove = 1;
4289 if (backwardMostMove == 0) backwardMostMove = 1;
4290 if (currentMove == 0) currentMove = 1;
4292 newGameMode = gameMode;
4293 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4295 case H_GOT_UNWANTED_HEADER:
4296 /* This is an initial board that we don't want */
4298 case H_GETTING_MOVES:
4299 /* Should not happen */
4300 DisplayError(_("Error gathering move list: extra board"), 0);
4301 ics_getting_history = H_FALSE;
4305 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4306 weird && (int)gameInfo.variant < (int)VariantShogi) {
4307 /* [HGM] We seem to have switched variant unexpectedly
4308 * Try to guess new variant from board size
4310 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4311 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4312 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4313 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4314 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4315 if(!weird) newVariant = VariantNormal;
4316 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4317 /* Get a move list just to see the header, which
4318 will tell us whether this is really bug or zh */
4319 if (ics_getting_history == H_FALSE) {
4320 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4321 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4326 /* Take action if this is the first board of a new game, or of a
4327 different game than is currently being displayed. */
4328 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4329 relation == RELATION_ISOLATED_BOARD) {
4331 /* Forget the old game and get the history (if any) of the new one */
4332 if (gameMode != BeginningOfGame) {
4336 if (appData.autoRaiseBoard) BoardToTop();
4338 if (gamenum == -1) {
4339 newGameMode = IcsIdle;
4340 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4341 appData.getMoveList && !reqFlag) {
4342 /* Need to get game history */
4343 ics_getting_history = H_REQUESTED;
4344 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4348 /* Initially flip the board to have black on the bottom if playing
4349 black or if the ICS flip flag is set, but let the user change
4350 it with the Flip View button. */
4351 flipView = appData.autoFlipView ?
4352 (newGameMode == IcsPlayingBlack) || ics_flip :
4355 /* Done with values from previous mode; copy in new ones */
4356 gameMode = newGameMode;
4358 ics_gamenum = gamenum;
4359 if (gamenum == gs_gamenum) {
4360 int klen = strlen(gs_kind);
4361 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4362 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4363 gameInfo.event = StrSave(str);
4365 gameInfo.event = StrSave("ICS game");
4367 gameInfo.site = StrSave(appData.icsHost);
4368 gameInfo.date = PGNDate();
4369 gameInfo.round = StrSave("-");
4370 gameInfo.white = StrSave(white);
4371 gameInfo.black = StrSave(black);
4372 timeControl = basetime * 60 * 1000;
4374 timeIncrement = increment * 1000;
4375 movesPerSession = 0;
4376 gameInfo.timeControl = TimeControlTagValue();
4377 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4378 if (appData.debugMode) {
4379 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4380 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4381 setbuf(debugFP, NULL);
4384 gameInfo.outOfBook = NULL;
4386 /* Do we have the ratings? */
4387 if (strcmp(player1Name, white) == 0 &&
4388 strcmp(player2Name, black) == 0) {
4389 if (appData.debugMode)
4390 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4391 player1Rating, player2Rating);
4392 gameInfo.whiteRating = player1Rating;
4393 gameInfo.blackRating = player2Rating;
4394 } else if (strcmp(player2Name, white) == 0 &&
4395 strcmp(player1Name, black) == 0) {
4396 if (appData.debugMode)
4397 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398 player2Rating, player1Rating);
4399 gameInfo.whiteRating = player2Rating;
4400 gameInfo.blackRating = player1Rating;
4402 player1Name[0] = player2Name[0] = NULLCHAR;
4404 /* Silence shouts if requested */
4405 if (appData.quietPlay &&
4406 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4407 SendToICS(ics_prefix);
4408 SendToICS("set shout 0\n");
4412 /* Deal with midgame name changes */
4414 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4415 if (gameInfo.white) free(gameInfo.white);
4416 gameInfo.white = StrSave(white);
4418 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4419 if (gameInfo.black) free(gameInfo.black);
4420 gameInfo.black = StrSave(black);
4424 /* Throw away game result if anything actually changes in examine mode */
4425 if (gameMode == IcsExamining && !newGame) {
4426 gameInfo.result = GameUnfinished;
4427 if (gameInfo.resultDetails != NULL) {
4428 free(gameInfo.resultDetails);
4429 gameInfo.resultDetails = NULL;
4433 /* In pausing && IcsExamining mode, we ignore boards coming
4434 in if they are in a different variation than we are. */
4435 if (pauseExamInvalid) return;
4436 if (pausing && gameMode == IcsExamining) {
4437 if (moveNum <= pauseExamForwardMostMove) {
4438 pauseExamInvalid = TRUE;
4439 forwardMostMove = pauseExamForwardMostMove;
4444 if (appData.debugMode) {
4445 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4447 /* Parse the board */
4448 for (k = 0; k < ranks; k++) {
4449 for (j = 0; j < files; j++)
4450 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4451 if(gameInfo.holdingsWidth > 1) {
4452 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4453 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4456 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4457 board[5][BOARD_RGHT+1] = WhiteAngel;
4458 board[6][BOARD_RGHT+1] = WhiteMarshall;
4459 board[1][0] = BlackMarshall;
4460 board[2][0] = BlackAngel;
4461 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4463 CopyBoard(boards[moveNum], board);
4464 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4466 startedFromSetupPosition =
4467 !CompareBoards(board, initialPosition);
4468 if(startedFromSetupPosition)
4469 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4472 /* [HGM] Set castling rights. Take the outermost Rooks,
4473 to make it also work for FRC opening positions. Note that board12
4474 is really defective for later FRC positions, as it has no way to
4475 indicate which Rook can castle if they are on the same side of King.
4476 For the initial position we grant rights to the outermost Rooks,
4477 and remember thos rights, and we then copy them on positions
4478 later in an FRC game. This means WB might not recognize castlings with
4479 Rooks that have moved back to their original position as illegal,
4480 but in ICS mode that is not its job anyway.
4482 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4483 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4485 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4486 if(board[0][i] == WhiteRook) j = i;
4487 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4488 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4489 if(board[0][i] == WhiteRook) j = i;
4490 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4491 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4492 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4493 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4494 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4495 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4496 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4499 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4500 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4501 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4502 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4503 if(board[BOARD_HEIGHT-1][k] == bKing)
4504 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4505 if(gameInfo.variant == VariantTwoKings) {
4506 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4507 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4508 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4511 r = boards[moveNum][CASTLING][0] = initialRights[0];
4512 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4513 r = boards[moveNum][CASTLING][1] = initialRights[1];
4514 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4515 r = boards[moveNum][CASTLING][3] = initialRights[3];
4516 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4517 r = boards[moveNum][CASTLING][4] = initialRights[4];
4518 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4519 /* wildcastle kludge: always assume King has rights */
4520 r = boards[moveNum][CASTLING][2] = initialRights[2];
4521 r = boards[moveNum][CASTLING][5] = initialRights[5];
4523 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4524 boards[moveNum][EP_STATUS] = EP_NONE;
4525 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4526 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4527 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4530 if (ics_getting_history == H_GOT_REQ_HEADER ||
4531 ics_getting_history == H_GOT_UNREQ_HEADER) {
4532 /* This was an initial position from a move list, not
4533 the current position */
4537 /* Update currentMove and known move number limits */
4538 newMove = newGame || moveNum > forwardMostMove;
4541 forwardMostMove = backwardMostMove = currentMove = moveNum;
4542 if (gameMode == IcsExamining && moveNum == 0) {
4543 /* Workaround for ICS limitation: we are not told the wild
4544 type when starting to examine a game. But if we ask for
4545 the move list, the move list header will tell us */
4546 ics_getting_history = H_REQUESTED;
4547 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4551 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4553 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4554 /* [HGM] applied this also to an engine that is silently watching */
4555 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4556 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4557 gameInfo.variant == currentlyInitializedVariant) {
4558 takeback = forwardMostMove - moveNum;
4559 for (i = 0; i < takeback; i++) {
4560 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4561 SendToProgram("undo\n", &first);
4566 forwardMostMove = moveNum;
4567 if (!pausing || currentMove > forwardMostMove)
4568 currentMove = forwardMostMove;
4570 /* New part of history that is not contiguous with old part */
4571 if (pausing && gameMode == IcsExamining) {
4572 pauseExamInvalid = TRUE;
4573 forwardMostMove = pauseExamForwardMostMove;
4576 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4578 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4579 // [HGM] when we will receive the move list we now request, it will be
4580 // fed to the engine from the first move on. So if the engine is not
4581 // in the initial position now, bring it there.
4582 InitChessProgram(&first, 0);
4585 ics_getting_history = H_REQUESTED;
4586 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4589 forwardMostMove = backwardMostMove = currentMove = moveNum;
4592 /* Update the clocks */
4593 if (strchr(elapsed_time, '.')) {
4595 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4596 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4598 /* Time is in seconds */
4599 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4600 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4605 if (appData.zippyPlay && newGame &&
4606 gameMode != IcsObserving && gameMode != IcsIdle &&
4607 gameMode != IcsExamining)
4608 ZippyFirstBoard(moveNum, basetime, increment);
4611 /* Put the move on the move list, first converting
4612 to canonical algebraic form. */
4614 if (appData.debugMode) {
4615 if (appData.debugMode) { int f = forwardMostMove;
4616 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4617 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4618 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4620 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4621 fprintf(debugFP, "moveNum = %d\n", moveNum);
4622 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4623 setbuf(debugFP, NULL);
4625 if (moveNum <= backwardMostMove) {
4626 /* We don't know what the board looked like before
4628 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4629 strcat(parseList[moveNum - 1], " ");
4630 strcat(parseList[moveNum - 1], elapsed_time);
4631 moveList[moveNum - 1][0] = NULLCHAR;
4632 } else if (strcmp(move_str, "none") == 0) {
4633 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4634 /* Again, we don't know what the board looked like;
4635 this is really the start of the game. */
4636 parseList[moveNum - 1][0] = NULLCHAR;
4637 moveList[moveNum - 1][0] = NULLCHAR;
4638 backwardMostMove = moveNum;
4639 startedFromSetupPosition = TRUE;
4640 fromX = fromY = toX = toY = -1;
4642 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4643 // So we parse the long-algebraic move string in stead of the SAN move
4644 int valid; char buf[MSG_SIZ], *prom;
4646 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4647 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4648 // str looks something like "Q/a1-a2"; kill the slash
4650 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4651 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4652 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4653 strcat(buf, prom); // long move lacks promo specification!
4654 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4655 if(appData.debugMode)
4656 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4657 safeStrCpy(move_str, buf, MSG_SIZ);
4659 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4660 &fromX, &fromY, &toX, &toY, &promoChar)
4661 || ParseOneMove(buf, moveNum - 1, &moveType,
4662 &fromX, &fromY, &toX, &toY, &promoChar);
4663 // end of long SAN patch
4665 (void) CoordsToAlgebraic(boards[moveNum - 1],
4666 PosFlags(moveNum - 1),
4667 fromY, fromX, toY, toX, promoChar,
4668 parseList[moveNum-1]);
4669 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4675 if(gameInfo.variant != VariantShogi)
4676 strcat(parseList[moveNum - 1], "+");
4679 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4680 strcat(parseList[moveNum - 1], "#");
4683 strcat(parseList[moveNum - 1], " ");
4684 strcat(parseList[moveNum - 1], elapsed_time);
4685 /* currentMoveString is set as a side-effect of ParseOneMove */
4686 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4687 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4688 strcat(moveList[moveNum - 1], "\n");
4690 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4691 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4692 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4693 ChessSquare old, new = boards[moveNum][k][j];
4694 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4695 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4696 if(old == new) continue;
4697 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4698 else if(new == WhiteWazir || new == BlackWazir) {
4699 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4700 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4701 else boards[moveNum][k][j] = old; // preserve type of Gold
4702 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4703 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4706 /* Move from ICS was illegal!? Punt. */
4707 if (appData.debugMode) {
4708 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4709 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4711 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4712 strcat(parseList[moveNum - 1], " ");
4713 strcat(parseList[moveNum - 1], elapsed_time);
4714 moveList[moveNum - 1][0] = NULLCHAR;
4715 fromX = fromY = toX = toY = -1;
4718 if (appData.debugMode) {
4719 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4720 setbuf(debugFP, NULL);
4724 /* Send move to chess program (BEFORE animating it). */
4725 if (appData.zippyPlay && !newGame && newMove &&
4726 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4728 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4729 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4730 if (moveList[moveNum - 1][0] == NULLCHAR) {
4731 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4733 DisplayError(str, 0);
4735 if (first.sendTime) {
4736 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4738 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4739 if (firstMove && !bookHit) {
4741 if (first.useColors) {
4742 SendToProgram(gameMode == IcsPlayingWhite ?
4744 "black\ngo\n", &first);
4746 SendToProgram("go\n", &first);
4748 first.maybeThinking = TRUE;
4751 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4752 if (moveList[moveNum - 1][0] == NULLCHAR) {
4753 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4754 DisplayError(str, 0);
4756 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4757 SendMoveToProgram(moveNum - 1, &first);
4764 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4765 /* If move comes from a remote source, animate it. If it
4766 isn't remote, it will have already been animated. */
4767 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4768 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4770 if (!pausing && appData.highlightLastMove) {
4771 SetHighlights(fromX, fromY, toX, toY);
4775 /* Start the clocks */
4776 whiteFlag = blackFlag = FALSE;
4777 appData.clockMode = !(basetime == 0 && increment == 0);
4779 ics_clock_paused = TRUE;
4781 } else if (ticking == 1) {
4782 ics_clock_paused = FALSE;
4784 if (gameMode == IcsIdle ||
4785 relation == RELATION_OBSERVING_STATIC ||
4786 relation == RELATION_EXAMINING ||
4788 DisplayBothClocks();
4792 /* Display opponents and material strengths */
4793 if (gameInfo.variant != VariantBughouse &&
4794 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4795 if (tinyLayout || smallLayout) {
4796 if(gameInfo.variant == VariantNormal)
4797 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4798 gameInfo.white, white_stren, gameInfo.black, black_stren,
4799 basetime, increment);
4801 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4802 gameInfo.white, white_stren, gameInfo.black, black_stren,
4803 basetime, increment, (int) gameInfo.variant);
4805 if(gameInfo.variant == VariantNormal)
4806 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4807 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4808 basetime, increment);
4810 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4811 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4812 basetime, increment, VariantName(gameInfo.variant));
4815 if (appData.debugMode) {
4816 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4821 /* Display the board */
4822 if (!pausing && !appData.noGUI) {
4824 if (appData.premove)
4826 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4827 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4828 ClearPremoveHighlights();
4830 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4831 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4832 DrawPosition(j, boards[currentMove]);
4834 DisplayMove(moveNum - 1);
4835 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4836 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4837 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4838 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4842 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4844 if(bookHit) { // [HGM] book: simulate book reply
4845 static char bookMove[MSG_SIZ]; // a bit generous?
4847 programStats.nodes = programStats.depth = programStats.time =
4848 programStats.score = programStats.got_only_move = 0;
4849 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4851 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4852 strcat(bookMove, bookHit);
4853 HandleMachineMove(bookMove, &first);
4862 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4863 ics_getting_history = H_REQUESTED;
4864 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4870 AnalysisPeriodicEvent (int force)
4872 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4873 && !force) || !appData.periodicUpdates)
4876 /* Send . command to Crafty to collect stats */
4877 SendToProgram(".\n", &first);
4879 /* Don't send another until we get a response (this makes
4880 us stop sending to old Crafty's which don't understand
4881 the "." command (sending illegal cmds resets node count & time,
4882 which looks bad)) */
4883 programStats.ok_to_send = 0;
4887 ics_update_width (int new_width)
4889 ics_printf("set width %d\n", new_width);
4893 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4897 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4898 // null move in variant where engine does not understand it (for analysis purposes)
4899 SendBoard(cps, moveNum + 1); // send position after move in stead.
4902 if (cps->useUsermove) {
4903 SendToProgram("usermove ", cps);
4907 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4908 int len = space - parseList[moveNum];
4909 memcpy(buf, parseList[moveNum], len);
4911 buf[len] = NULLCHAR;
4913 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4915 SendToProgram(buf, cps);
4917 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4918 AlphaRank(moveList[moveNum], 4);
4919 SendToProgram(moveList[moveNum], cps);
4920 AlphaRank(moveList[moveNum], 4); // and back
4922 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4923 * the engine. It would be nice to have a better way to identify castle
4925 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4926 && cps->useOOCastle) {
4927 int fromX = moveList[moveNum][0] - AAA;
4928 int fromY = moveList[moveNum][1] - ONE;
4929 int toX = moveList[moveNum][2] - AAA;
4930 int toY = moveList[moveNum][3] - ONE;
4931 if((boards[moveNum][fromY][fromX] == WhiteKing
4932 && boards[moveNum][toY][toX] == WhiteRook)
4933 || (boards[moveNum][fromY][fromX] == BlackKing
4934 && boards[moveNum][toY][toX] == BlackRook)) {
4935 if(toX > fromX) SendToProgram("O-O\n", cps);
4936 else SendToProgram("O-O-O\n", cps);
4938 else SendToProgram(moveList[moveNum], cps);
4940 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4941 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4942 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4943 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4944 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4946 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4947 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4948 SendToProgram(buf, cps);
4950 else SendToProgram(moveList[moveNum], cps);
4951 /* End of additions by Tord */
4954 /* [HGM] setting up the opening has brought engine in force mode! */
4955 /* Send 'go' if we are in a mode where machine should play. */
4956 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4957 (gameMode == TwoMachinesPlay ||
4959 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4961 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4962 SendToProgram("go\n", cps);
4963 if (appData.debugMode) {
4964 fprintf(debugFP, "(extra)\n");
4967 setboardSpoiledMachineBlack = 0;
4971 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4973 char user_move[MSG_SIZ];
4976 if(gameInfo.variant == VariantSChess && promoChar) {
4977 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4978 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4979 } else suffix[0] = NULLCHAR;
4983 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4984 (int)moveType, fromX, fromY, toX, toY);
4985 DisplayError(user_move + strlen("say "), 0);
4987 case WhiteKingSideCastle:
4988 case BlackKingSideCastle:
4989 case WhiteQueenSideCastleWild:
4990 case BlackQueenSideCastleWild:
4992 case WhiteHSideCastleFR:
4993 case BlackHSideCastleFR:
4995 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4997 case WhiteQueenSideCastle:
4998 case BlackQueenSideCastle:
4999 case WhiteKingSideCastleWild:
5000 case BlackKingSideCastleWild:
5002 case WhiteASideCastleFR:
5003 case BlackASideCastleFR:
5005 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5007 case WhiteNonPromotion:
5008 case BlackNonPromotion:
5009 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5011 case WhitePromotion:
5012 case BlackPromotion:
5013 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5014 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5015 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5016 PieceToChar(WhiteFerz));
5017 else if(gameInfo.variant == VariantGreat)
5018 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5019 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020 PieceToChar(WhiteMan));
5022 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5023 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5029 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5030 ToUpper(PieceToChar((ChessSquare) fromX)),
5031 AAA + toX, ONE + toY);
5033 case IllegalMove: /* could be a variant we don't quite understand */
5034 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5036 case WhiteCapturesEnPassant:
5037 case BlackCapturesEnPassant:
5038 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5039 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5042 SendToICS(user_move);
5043 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5044 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5049 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5050 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5051 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5052 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5053 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5056 if(gameMode != IcsExamining) { // is this ever not the case?
5057 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5059 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5060 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5061 } else { // on FICS we must first go to general examine mode
5062 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5064 if(gameInfo.variant != VariantNormal) {
5065 // try figure out wild number, as xboard names are not always valid on ICS
5066 for(i=1; i<=36; i++) {
5067 snprintf(buf, MSG_SIZ, "wild/%d", i);
5068 if(StringToVariant(buf) == gameInfo.variant) break;
5070 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5071 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5072 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5073 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5074 SendToICS(ics_prefix);
5076 if(startedFromSetupPosition || backwardMostMove != 0) {
5077 fen = PositionToFEN(backwardMostMove, NULL);
5078 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5079 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5081 } else { // FICS: everything has to set by separate bsetup commands
5082 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5083 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5085 if(!WhiteOnMove(backwardMostMove)) {
5086 SendToICS("bsetup tomove black\n");
5088 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5089 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5091 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5092 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5094 i = boards[backwardMostMove][EP_STATUS];
5095 if(i >= 0) { // set e.p.
5096 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5102 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5103 SendToICS("bsetup done\n"); // switch to normal examining.
5105 for(i = backwardMostMove; i<last; i++) {
5107 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5110 SendToICS(ics_prefix);
5111 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5115 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5117 if (rf == DROP_RANK) {
5118 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5119 sprintf(move, "%c@%c%c\n",
5120 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5122 if (promoChar == 'x' || promoChar == NULLCHAR) {
5123 sprintf(move, "%c%c%c%c\n",
5124 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5126 sprintf(move, "%c%c%c%c%c\n",
5127 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5133 ProcessICSInitScript (FILE *f)
5137 while (fgets(buf, MSG_SIZ, f)) {
5138 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5145 static int lastX, lastY, selectFlag, dragging;
5150 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5151 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5152 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5153 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5154 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5155 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5158 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5159 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5160 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5161 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5162 if(!step) step = -1;
5163 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5164 appData.testLegality && (promoSweep == king ||
5165 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5166 ChangeDragPiece(promoSweep);
5170 PromoScroll (int x, int y)
5174 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5175 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5176 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5177 if(!step) return FALSE;
5178 lastX = x; lastY = y;
5179 if((promoSweep < BlackPawn) == flipView) step = -step;
5180 if(step > 0) selectFlag = 1;
5181 if(!selectFlag) Sweep(step);
5186 NextPiece (int step)
5188 ChessSquare piece = boards[currentMove][toY][toX];
5191 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5192 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5193 if(!step) step = -1;
5194 } while(PieceToChar(pieceSweep) == '.');
5195 boards[currentMove][toY][toX] = pieceSweep;
5196 DrawPosition(FALSE, boards[currentMove]);
5197 boards[currentMove][toY][toX] = piece;
5199 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5201 AlphaRank (char *move, int n)
5203 // char *p = move, c; int x, y;
5205 if (appData.debugMode) {
5206 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5210 move[2]>='0' && move[2]<='9' &&
5211 move[3]>='a' && move[3]<='x' ) {
5213 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5214 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5216 if(move[0]>='0' && move[0]<='9' &&
5217 move[1]>='a' && move[1]<='x' &&
5218 move[2]>='0' && move[2]<='9' &&
5219 move[3]>='a' && move[3]<='x' ) {
5220 /* input move, Shogi -> normal */
5221 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5222 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5223 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5224 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5227 move[3]>='0' && move[3]<='9' &&
5228 move[2]>='a' && move[2]<='x' ) {
5230 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5231 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5234 move[0]>='a' && move[0]<='x' &&
5235 move[3]>='0' && move[3]<='9' &&
5236 move[2]>='a' && move[2]<='x' ) {
5237 /* output move, normal -> Shogi */
5238 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5239 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5240 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5241 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5242 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5244 if (appData.debugMode) {
5245 fprintf(debugFP, " out = '%s'\n", move);
5249 char yy_textstr[8000];
5251 /* Parser for moves from gnuchess, ICS, or user typein box */
5253 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5255 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5257 switch (*moveType) {
5258 case WhitePromotion:
5259 case BlackPromotion:
5260 case WhiteNonPromotion:
5261 case BlackNonPromotion:
5263 case WhiteCapturesEnPassant:
5264 case BlackCapturesEnPassant:
5265 case WhiteKingSideCastle:
5266 case WhiteQueenSideCastle:
5267 case BlackKingSideCastle:
5268 case BlackQueenSideCastle:
5269 case WhiteKingSideCastleWild:
5270 case WhiteQueenSideCastleWild:
5271 case BlackKingSideCastleWild:
5272 case BlackQueenSideCastleWild:
5273 /* Code added by Tord: */
5274 case WhiteHSideCastleFR:
5275 case WhiteASideCastleFR:
5276 case BlackHSideCastleFR:
5277 case BlackASideCastleFR:
5278 /* End of code added by Tord */
5279 case IllegalMove: /* bug or odd chess variant */
5280 *fromX = currentMoveString[0] - AAA;
5281 *fromY = currentMoveString[1] - ONE;
5282 *toX = currentMoveString[2] - AAA;
5283 *toY = currentMoveString[3] - ONE;
5284 *promoChar = currentMoveString[4];
5285 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5286 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5287 if (appData.debugMode) {
5288 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5290 *fromX = *fromY = *toX = *toY = 0;
5293 if (appData.testLegality) {
5294 return (*moveType != IllegalMove);
5296 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5297 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5302 *fromX = *moveType == WhiteDrop ?
5303 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5304 (int) CharToPiece(ToLower(currentMoveString[0]));
5306 *toX = currentMoveString[2] - AAA;
5307 *toY = currentMoveString[3] - ONE;
5308 *promoChar = NULLCHAR;
5312 case ImpossibleMove:
5322 if (appData.debugMode) {
5323 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5326 *fromX = *fromY = *toX = *toY = 0;
5327 *promoChar = NULLCHAR;
5332 Boolean pushed = FALSE;
5333 char *lastParseAttempt;
5336 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5337 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5338 int fromX, fromY, toX, toY; char promoChar;
5343 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5344 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5347 endPV = forwardMostMove;
5349 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5350 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5351 lastParseAttempt = pv;
5352 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5353 if(!valid && nr == 0 &&
5354 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5355 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5356 // Hande case where played move is different from leading PV move
5357 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5358 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5359 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5360 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5361 endPV += 2; // if position different, keep this
5362 moveList[endPV-1][0] = fromX + AAA;
5363 moveList[endPV-1][1] = fromY + ONE;
5364 moveList[endPV-1][2] = toX + AAA;
5365 moveList[endPV-1][3] = toY + ONE;
5366 parseList[endPV-1][0] = NULLCHAR;
5367 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5370 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5371 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5372 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5373 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5374 valid++; // allow comments in PV
5378 if(endPV+1 > framePtr) break; // no space, truncate
5381 CopyBoard(boards[endPV], boards[endPV-1]);
5382 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5383 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5384 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5385 CoordsToAlgebraic(boards[endPV - 1],
5386 PosFlags(endPV - 1),
5387 fromY, fromX, toY, toX, promoChar,
5388 parseList[endPV - 1]);
5390 if(atEnd == 2) return; // used hidden, for PV conversion
5391 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5392 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5393 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5394 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5395 DrawPosition(TRUE, boards[currentMove]);
5399 MultiPV (ChessProgramState *cps)
5400 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5402 for(i=0; i<cps->nrOptions; i++)
5403 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5409 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5411 int startPV, multi, lineStart, origIndex = index;
5412 char *p, buf2[MSG_SIZ];
5414 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5415 lastX = x; lastY = y;
5416 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5417 lineStart = startPV = index;
5418 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5419 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5421 do{ while(buf[index] && buf[index] != '\n') index++;
5422 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5424 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5425 int n = first.option[multi].value;
5426 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5427 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5428 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5429 first.option[multi].value = n;
5432 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5433 ExcludeClick(origIndex - lineStart);
5436 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5437 *start = startPV; *end = index-1;
5444 static char buf[10*MSG_SIZ];
5445 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5447 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5448 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5449 for(i = forwardMostMove; i<endPV; i++){
5450 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5451 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5454 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5455 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5461 LoadPV (int x, int y)
5462 { // called on right mouse click to load PV
5463 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5464 lastX = x; lastY = y;
5465 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5472 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5473 if(endPV < 0) return;
5474 if(appData.autoCopyPV) CopyFENToClipboard();
5476 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5477 Boolean saveAnimate = appData.animate;
5479 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5480 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5481 } else storedGames--; // abandon shelved tail of original game
5484 forwardMostMove = currentMove;
5485 currentMove = oldFMM;
5486 appData.animate = FALSE;
5487 ToNrEvent(forwardMostMove);
5488 appData.animate = saveAnimate;
5490 currentMove = forwardMostMove;
5491 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5492 ClearPremoveHighlights();
5493 DrawPosition(TRUE, boards[currentMove]);
5497 MovePV (int x, int y, int h)
5498 { // step through PV based on mouse coordinates (called on mouse move)
5499 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5501 // we must somehow check if right button is still down (might be released off board!)
5502 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5503 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5504 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5506 lastX = x; lastY = y;
5508 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5509 if(endPV < 0) return;
5510 if(y < margin) step = 1; else
5511 if(y > h - margin) step = -1;
5512 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5513 currentMove += step;
5514 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5515 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5516 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5517 DrawPosition(FALSE, boards[currentMove]);
5521 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5522 // All positions will have equal probability, but the current method will not provide a unique
5523 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5529 int piecesLeft[(int)BlackPawn];
5530 int seed, nrOfShuffles;
5533 GetPositionNumber ()
5534 { // sets global variable seed
5537 seed = appData.defaultFrcPosition;
5538 if(seed < 0) { // randomize based on time for negative FRC position numbers
5539 for(i=0; i<50; i++) seed += random();
5540 seed = random() ^ random() >> 8 ^ random() << 8;
5541 if(seed<0) seed = -seed;
5546 put (Board board, int pieceType, int rank, int n, int shade)
5547 // put the piece on the (n-1)-th empty squares of the given shade
5551 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5552 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5553 board[rank][i] = (ChessSquare) pieceType;
5554 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5556 piecesLeft[pieceType]--;
5565 AddOnePiece (Board board, int pieceType, int rank, int shade)
5566 // calculate where the next piece goes, (any empty square), and put it there
5570 i = seed % squaresLeft[shade];
5571 nrOfShuffles *= squaresLeft[shade];
5572 seed /= squaresLeft[shade];
5573 put(board, pieceType, rank, i, shade);
5577 AddTwoPieces (Board board, int pieceType, int rank)
5578 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5580 int i, n=squaresLeft[ANY], j=n-1, k;
5582 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5583 i = seed % k; // pick one
5586 while(i >= j) i -= j--;
5587 j = n - 1 - j; i += j;
5588 put(board, pieceType, rank, j, ANY);
5589 put(board, pieceType, rank, i, ANY);
5593 SetUpShuffle (Board board, int number)
5597 GetPositionNumber(); nrOfShuffles = 1;
5599 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5600 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5601 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5603 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5605 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5606 p = (int) board[0][i];
5607 if(p < (int) BlackPawn) piecesLeft[p] ++;
5608 board[0][i] = EmptySquare;
5611 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5612 // shuffles restricted to allow normal castling put KRR first
5613 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5614 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5615 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5616 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5617 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5618 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5619 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5620 put(board, WhiteRook, 0, 0, ANY);
5621 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5624 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5625 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5626 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5627 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5628 while(piecesLeft[p] >= 2) {
5629 AddOnePiece(board, p, 0, LITE);
5630 AddOnePiece(board, p, 0, DARK);
5632 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5635 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5636 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5637 // but we leave King and Rooks for last, to possibly obey FRC restriction
5638 if(p == (int)WhiteRook) continue;
5639 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5640 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5643 // now everything is placed, except perhaps King (Unicorn) and Rooks
5645 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5646 // Last King gets castling rights
5647 while(piecesLeft[(int)WhiteUnicorn]) {
5648 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5649 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5652 while(piecesLeft[(int)WhiteKing]) {
5653 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5654 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5659 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5660 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5663 // Only Rooks can be left; simply place them all
5664 while(piecesLeft[(int)WhiteRook]) {
5665 i = put(board, WhiteRook, 0, 0, ANY);
5666 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5669 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5671 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5674 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5675 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5678 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5682 SetCharTable (char *table, const char * map)
5683 /* [HGM] moved here from winboard.c because of its general usefulness */
5684 /* Basically a safe strcpy that uses the last character as King */
5686 int result = FALSE; int NrPieces;
5688 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5689 && NrPieces >= 12 && !(NrPieces&1)) {
5690 int i; /* [HGM] Accept even length from 12 to 34 */
5692 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5693 for( i=0; i<NrPieces/2-1; i++ ) {
5695 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5697 table[(int) WhiteKing] = map[NrPieces/2-1];
5698 table[(int) BlackKing] = map[NrPieces-1];
5707 Prelude (Board board)
5708 { // [HGM] superchess: random selection of exo-pieces
5709 int i, j, k; ChessSquare p;
5710 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5712 GetPositionNumber(); // use FRC position number
5714 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5715 SetCharTable(pieceToChar, appData.pieceToCharTable);
5716 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5717 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5720 j = seed%4; seed /= 4;
5721 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5722 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5723 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5724 j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5729 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5737 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5738 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5739 put(board, exoPieces[0], 0, 0, ANY);
5740 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5744 InitPosition (int redraw)
5746 ChessSquare (* pieces)[BOARD_FILES];
5747 int i, j, pawnRow, overrule,
5748 oldx = gameInfo.boardWidth,
5749 oldy = gameInfo.boardHeight,
5750 oldh = gameInfo.holdingsWidth;
5753 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5755 /* [AS] Initialize pv info list [HGM] and game status */
5757 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5758 pvInfoList[i].depth = 0;
5759 boards[i][EP_STATUS] = EP_NONE;
5760 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5763 initialRulePlies = 0; /* 50-move counter start */
5765 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5766 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5770 /* [HGM] logic here is completely changed. In stead of full positions */
5771 /* the initialized data only consist of the two backranks. The switch */
5772 /* selects which one we will use, which is than copied to the Board */
5773 /* initialPosition, which for the rest is initialized by Pawns and */
5774 /* empty squares. This initial position is then copied to boards[0], */
5775 /* possibly after shuffling, so that it remains available. */
5777 gameInfo.holdingsWidth = 0; /* default board sizes */
5778 gameInfo.boardWidth = 8;
5779 gameInfo.boardHeight = 8;
5780 gameInfo.holdingsSize = 0;
5781 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5782 for(i=0; i<BOARD_FILES-2; i++)
5783 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5784 initialPosition[EP_STATUS] = EP_NONE;
5785 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5786 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5787 SetCharTable(pieceNickName, appData.pieceNickNames);
5788 else SetCharTable(pieceNickName, "............");
5791 switch (gameInfo.variant) {
5792 case VariantFischeRandom:
5793 shuffleOpenings = TRUE;
5796 case VariantShatranj:
5797 pieces = ShatranjArray;
5798 nrCastlingRights = 0;
5799 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5802 pieces = makrukArray;
5803 nrCastlingRights = 0;
5804 startedFromSetupPosition = TRUE;
5805 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5807 case VariantTwoKings:
5808 pieces = twoKingsArray;
5811 pieces = GrandArray;
5812 nrCastlingRights = 0;
5813 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5814 gameInfo.boardWidth = 10;
5815 gameInfo.boardHeight = 10;
5816 gameInfo.holdingsSize = 7;
5818 case VariantCapaRandom:
5819 shuffleOpenings = TRUE;
5820 case VariantCapablanca:
5821 pieces = CapablancaArray;
5822 gameInfo.boardWidth = 10;
5823 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5826 pieces = GothicArray;
5827 gameInfo.boardWidth = 10;
5828 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5831 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5832 gameInfo.holdingsSize = 7;
5835 pieces = JanusArray;
5836 gameInfo.boardWidth = 10;
5837 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5838 nrCastlingRights = 6;
5839 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5840 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5841 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5842 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5843 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5844 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5847 pieces = FalconArray;
5848 gameInfo.boardWidth = 10;
5849 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5851 case VariantXiangqi:
5852 pieces = XiangqiArray;
5853 gameInfo.boardWidth = 9;
5854 gameInfo.boardHeight = 10;
5855 nrCastlingRights = 0;
5856 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5859 pieces = ShogiArray;
5860 gameInfo.boardWidth = 9;
5861 gameInfo.boardHeight = 9;
5862 gameInfo.holdingsSize = 7;
5863 nrCastlingRights = 0;
5864 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5866 case VariantCourier:
5867 pieces = CourierArray;
5868 gameInfo.boardWidth = 12;
5869 nrCastlingRights = 0;
5870 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5872 case VariantKnightmate:
5873 pieces = KnightmateArray;
5874 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5876 case VariantSpartan:
5877 pieces = SpartanArray;
5878 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5881 pieces = fairyArray;
5882 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5885 pieces = GreatArray;
5886 gameInfo.boardWidth = 10;
5887 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5888 gameInfo.holdingsSize = 8;
5892 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5893 gameInfo.holdingsSize = 8;
5894 startedFromSetupPosition = TRUE;
5896 case VariantCrazyhouse:
5897 case VariantBughouse:
5899 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5900 gameInfo.holdingsSize = 5;
5902 case VariantWildCastle:
5904 /* !!?shuffle with kings guaranteed to be on d or e file */
5905 shuffleOpenings = 1;
5907 case VariantNoCastle:
5909 nrCastlingRights = 0;
5910 /* !!?unconstrained back-rank shuffle */
5911 shuffleOpenings = 1;
5916 if(appData.NrFiles >= 0) {
5917 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5918 gameInfo.boardWidth = appData.NrFiles;
5920 if(appData.NrRanks >= 0) {
5921 gameInfo.boardHeight = appData.NrRanks;
5923 if(appData.holdingsSize >= 0) {
5924 i = appData.holdingsSize;
5925 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5926 gameInfo.holdingsSize = i;
5928 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5929 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5930 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5932 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5933 if(pawnRow < 1) pawnRow = 1;
5934 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5936 /* User pieceToChar list overrules defaults */
5937 if(appData.pieceToCharTable != NULL)
5938 SetCharTable(pieceToChar, appData.pieceToCharTable);
5940 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5942 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5943 s = (ChessSquare) 0; /* account holding counts in guard band */
5944 for( i=0; i<BOARD_HEIGHT; i++ )
5945 initialPosition[i][j] = s;
5947 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5948 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5949 initialPosition[pawnRow][j] = WhitePawn;
5950 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5951 if(gameInfo.variant == VariantXiangqi) {
5953 initialPosition[pawnRow][j] =
5954 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5955 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5956 initialPosition[2][j] = WhiteCannon;
5957 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5961 if(gameInfo.variant == VariantGrand) {
5962 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5963 initialPosition[0][j] = WhiteRook;
5964 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5967 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5969 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5972 initialPosition[1][j] = WhiteBishop;
5973 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5975 initialPosition[1][j] = WhiteRook;
5976 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5979 if( nrCastlingRights == -1) {
5980 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5981 /* This sets default castling rights from none to normal corners */
5982 /* Variants with other castling rights must set them themselves above */
5983 nrCastlingRights = 6;
5985 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5986 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5987 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5988 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5989 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5990 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5993 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5994 if(gameInfo.variant == VariantGreat) { // promotion commoners
5995 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5996 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5997 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5998 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6000 if( gameInfo.variant == VariantSChess ) {
6001 initialPosition[1][0] = BlackMarshall;
6002 initialPosition[2][0] = BlackAngel;
6003 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6004 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6005 initialPosition[1][1] = initialPosition[2][1] =
6006 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6008 if (appData.debugMode) {
6009 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6011 if(shuffleOpenings) {
6012 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6013 startedFromSetupPosition = TRUE;
6015 if(startedFromPositionFile) {
6016 /* [HGM] loadPos: use PositionFile for every new game */
6017 CopyBoard(initialPosition, filePosition);
6018 for(i=0; i<nrCastlingRights; i++)
6019 initialRights[i] = filePosition[CASTLING][i];
6020 startedFromSetupPosition = TRUE;
6023 CopyBoard(boards[0], initialPosition);
6025 if(oldx != gameInfo.boardWidth ||
6026 oldy != gameInfo.boardHeight ||
6027 oldv != gameInfo.variant ||
6028 oldh != gameInfo.holdingsWidth
6030 InitDrawingSizes(-2 ,0);
6032 oldv = gameInfo.variant;
6034 DrawPosition(TRUE, boards[currentMove]);
6038 SendBoard (ChessProgramState *cps, int moveNum)
6040 char message[MSG_SIZ];
6042 if (cps->useSetboard) {
6043 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6044 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6045 SendToProgram(message, cps);
6050 int i, j, left=0, right=BOARD_WIDTH;
6051 /* Kludge to set black to move, avoiding the troublesome and now
6052 * deprecated "black" command.
6054 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6055 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6057 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6059 SendToProgram("edit\n", cps);
6060 SendToProgram("#\n", cps);
6061 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6062 bp = &boards[moveNum][i][left];
6063 for (j = left; j < right; j++, bp++) {
6064 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6065 if ((int) *bp < (int) BlackPawn) {
6066 if(j == BOARD_RGHT+1)
6067 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6068 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6069 if(message[0] == '+' || message[0] == '~') {
6070 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6071 PieceToChar((ChessSquare)(DEMOTED *bp)),
6074 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6075 message[1] = BOARD_RGHT - 1 - j + '1';
6076 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6078 SendToProgram(message, cps);
6083 SendToProgram("c\n", cps);
6084 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6085 bp = &boards[moveNum][i][left];
6086 for (j = left; j < right; j++, bp++) {
6087 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6088 if (((int) *bp != (int) EmptySquare)
6089 && ((int) *bp >= (int) BlackPawn)) {
6090 if(j == BOARD_LEFT-2)
6091 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6092 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6094 if(message[0] == '+' || message[0] == '~') {
6095 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6096 PieceToChar((ChessSquare)(DEMOTED *bp)),
6099 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6100 message[1] = BOARD_RGHT - 1 - j + '1';
6101 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6103 SendToProgram(message, cps);
6108 SendToProgram(".\n", cps);
6110 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6113 char exclusionHeader[MSG_SIZ];
6114 int exCnt, excludePtr;
6115 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6116 static Exclusion excluTab[200];
6117 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6123 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6124 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6131 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6132 excludePtr = 24; exCnt = 0;
6137 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6138 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6139 char buf[2*MOVE_LEN], *p;
6140 Exclusion *e = excluTab;
6142 for(i=0; i<exCnt; i++)
6143 if(e[i].ff == fromX && e[i].fr == fromY &&
6144 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6145 if(i == exCnt) { // was not in exclude list; add it
6146 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6147 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6148 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6151 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6152 excludePtr++; e[i].mark = excludePtr++;
6153 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6156 exclusionHeader[e[i].mark] = state;
6160 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6161 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6162 char *p, buf[MSG_SIZ];
6165 if(promoChar == -1) { // kludge to indicate best move
6166 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6167 return 1; // if unparsable, abort
6169 // update exclusion map (resolving toggle by consulting existing state)
6170 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6172 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6173 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6174 excludeMap[k] |= 1<<j;
6175 else excludeMap[k] &= ~(1<<j);
6177 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6179 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6180 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6181 SendToProgram(buf, &first);
6182 return (state == '+');
6186 ExcludeClick (int index)
6190 Exclusion *e = excluTab;
6191 if(index < 25) { // none, best or tail clicked
6192 if(index < 13) { // none: include all
6193 WriteMap(0); // clear map
6194 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6195 SendToProgram("include all\n", &first); // and inform engine
6196 } else if(index > 18) { // tail
6197 if(exclusionHeader[19] == '-') { // tail was excluded
6198 SendToProgram("include all\n", &first);
6199 WriteMap(0); // clear map completely
6200 // now re-exclude selected moves
6201 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6202 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6203 } else { // tail was included or in mixed state
6204 SendToProgram("exclude all\n", &first);
6205 WriteMap(0xFF); // fill map completely
6206 // now re-include selected moves
6207 j = 0; // count them
6208 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6209 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6210 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6213 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6216 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6217 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6218 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6225 DefaultPromoChoice (int white)
6228 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6229 result = WhiteFerz; // no choice
6230 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6231 result= WhiteKing; // in Suicide Q is the last thing we want
6232 else if(gameInfo.variant == VariantSpartan)
6233 result = white ? WhiteQueen : WhiteAngel;
6234 else result = WhiteQueen;
6235 if(!white) result = WHITE_TO_BLACK result;
6239 static int autoQueen; // [HGM] oneclick
6242 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6244 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6245 /* [HGM] add Shogi promotions */
6246 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6251 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6252 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6254 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6255 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6258 piece = boards[currentMove][fromY][fromX];
6259 if(gameInfo.variant == VariantShogi) {
6260 promotionZoneSize = BOARD_HEIGHT/3;
6261 highestPromotingPiece = (int)WhiteFerz;
6262 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6263 promotionZoneSize = 3;
6266 // Treat Lance as Pawn when it is not representing Amazon
6267 if(gameInfo.variant != VariantSuper) {
6268 if(piece == WhiteLance) piece = WhitePawn; else
6269 if(piece == BlackLance) piece = BlackPawn;
6272 // next weed out all moves that do not touch the promotion zone at all
6273 if((int)piece >= BlackPawn) {
6274 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6276 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6278 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6279 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6282 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6284 // weed out mandatory Shogi promotions
6285 if(gameInfo.variant == VariantShogi) {
6286 if(piece >= BlackPawn) {
6287 if(toY == 0 && piece == BlackPawn ||
6288 toY == 0 && piece == BlackQueen ||
6289 toY <= 1 && piece == BlackKnight) {
6294 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6295 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6296 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6303 // weed out obviously illegal Pawn moves
6304 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6305 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6306 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6307 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6308 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6309 // note we are not allowed to test for valid (non-)capture, due to premove
6312 // we either have a choice what to promote to, or (in Shogi) whether to promote
6313 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6314 *promoChoice = PieceToChar(BlackFerz); // no choice
6317 // no sense asking what we must promote to if it is going to explode...
6318 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6319 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6322 // give caller the default choice even if we will not make it
6323 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6324 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6325 if( sweepSelect && gameInfo.variant != VariantGreat
6326 && gameInfo.variant != VariantGrand
6327 && gameInfo.variant != VariantSuper) return FALSE;
6328 if(autoQueen) return FALSE; // predetermined
6330 // suppress promotion popup on illegal moves that are not premoves
6331 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6332 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6333 if(appData.testLegality && !premove) {
6334 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6335 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6336 if(moveType != WhitePromotion && moveType != BlackPromotion)
6344 InPalace (int row, int column)
6345 { /* [HGM] for Xiangqi */
6346 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6347 column < (BOARD_WIDTH + 4)/2 &&
6348 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6353 PieceForSquare (int x, int y)
6355 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6358 return boards[currentMove][y][x];
6362 OKToStartUserMove (int x, int y)
6364 ChessSquare from_piece;
6367 if (matchMode) return FALSE;
6368 if (gameMode == EditPosition) return TRUE;
6370 if (x >= 0 && y >= 0)
6371 from_piece = boards[currentMove][y][x];
6373 from_piece = EmptySquare;
6375 if (from_piece == EmptySquare) return FALSE;
6377 white_piece = (int)from_piece >= (int)WhitePawn &&
6378 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6382 case TwoMachinesPlay:
6390 case MachinePlaysWhite:
6391 case IcsPlayingBlack:
6392 if (appData.zippyPlay) return FALSE;
6394 DisplayMoveError(_("You are playing Black"));
6399 case MachinePlaysBlack:
6400 case IcsPlayingWhite:
6401 if (appData.zippyPlay) return FALSE;
6403 DisplayMoveError(_("You are playing White"));
6408 case PlayFromGameFile:
6409 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6411 if (!white_piece && WhiteOnMove(currentMove)) {
6412 DisplayMoveError(_("It is White's turn"));
6415 if (white_piece && !WhiteOnMove(currentMove)) {
6416 DisplayMoveError(_("It is Black's turn"));
6419 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6420 /* Editing correspondence game history */
6421 /* Could disallow this or prompt for confirmation */
6426 case BeginningOfGame:
6427 if (appData.icsActive) return FALSE;
6428 if (!appData.noChessProgram) {
6430 DisplayMoveError(_("You are playing White"));
6437 if (!white_piece && WhiteOnMove(currentMove)) {
6438 DisplayMoveError(_("It is White's turn"));
6441 if (white_piece && !WhiteOnMove(currentMove)) {
6442 DisplayMoveError(_("It is Black's turn"));
6451 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6452 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6453 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6454 && gameMode != AnalyzeFile && gameMode != Training) {
6455 DisplayMoveError(_("Displayed position is not current"));
6462 OnlyMove (int *x, int *y, Boolean captures)
6464 DisambiguateClosure cl;
6465 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6467 case MachinePlaysBlack:
6468 case IcsPlayingWhite:
6469 case BeginningOfGame:
6470 if(!WhiteOnMove(currentMove)) return FALSE;
6472 case MachinePlaysWhite:
6473 case IcsPlayingBlack:
6474 if(WhiteOnMove(currentMove)) return FALSE;
6481 cl.pieceIn = EmptySquare;
6486 cl.promoCharIn = NULLCHAR;
6487 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6488 if( cl.kind == NormalMove ||
6489 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6490 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6491 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6498 if(cl.kind != ImpossibleMove) return FALSE;
6499 cl.pieceIn = EmptySquare;
6504 cl.promoCharIn = NULLCHAR;
6505 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6506 if( cl.kind == NormalMove ||
6507 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6508 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6509 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6514 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6520 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6521 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6522 int lastLoadGameUseList = FALSE;
6523 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6524 ChessMove lastLoadGameStart = EndOfFile;
6528 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6531 ChessSquare pdown, pup;
6532 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6535 /* Check if the user is playing in turn. This is complicated because we
6536 let the user "pick up" a piece before it is his turn. So the piece he
6537 tried to pick up may have been captured by the time he puts it down!
6538 Therefore we use the color the user is supposed to be playing in this
6539 test, not the color of the piece that is currently on the starting
6540 square---except in EditGame mode, where the user is playing both
6541 sides; fortunately there the capture race can't happen. (It can
6542 now happen in IcsExamining mode, but that's just too bad. The user
6543 will get a somewhat confusing message in that case.)
6548 case TwoMachinesPlay:
6552 /* We switched into a game mode where moves are not accepted,
6553 perhaps while the mouse button was down. */
6556 case MachinePlaysWhite:
6557 /* User is moving for Black */
6558 if (WhiteOnMove(currentMove)) {
6559 DisplayMoveError(_("It is White's turn"));
6564 case MachinePlaysBlack:
6565 /* User is moving for White */
6566 if (!WhiteOnMove(currentMove)) {
6567 DisplayMoveError(_("It is Black's turn"));
6572 case PlayFromGameFile:
6573 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6576 case BeginningOfGame:
6579 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6580 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6581 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6582 /* User is moving for Black */
6583 if (WhiteOnMove(currentMove)) {
6584 DisplayMoveError(_("It is White's turn"));
6588 /* User is moving for White */
6589 if (!WhiteOnMove(currentMove)) {
6590 DisplayMoveError(_("It is Black's turn"));
6596 case IcsPlayingBlack:
6597 /* User is moving for Black */
6598 if (WhiteOnMove(currentMove)) {
6599 if (!appData.premove) {
6600 DisplayMoveError(_("It is White's turn"));
6601 } else if (toX >= 0 && toY >= 0) {
6604 premoveFromX = fromX;
6605 premoveFromY = fromY;
6606 premovePromoChar = promoChar;
6608 if (appData.debugMode)
6609 fprintf(debugFP, "Got premove: fromX %d,"
6610 "fromY %d, toX %d, toY %d\n",
6611 fromX, fromY, toX, toY);
6617 case IcsPlayingWhite:
6618 /* User is moving for White */
6619 if (!WhiteOnMove(currentMove)) {
6620 if (!appData.premove) {
6621 DisplayMoveError(_("It is Black's turn"));
6622 } else if (toX >= 0 && toY >= 0) {
6625 premoveFromX = fromX;
6626 premoveFromY = fromY;
6627 premovePromoChar = promoChar;
6629 if (appData.debugMode)
6630 fprintf(debugFP, "Got premove: fromX %d,"
6631 "fromY %d, toX %d, toY %d\n",
6632 fromX, fromY, toX, toY);
6642 /* EditPosition, empty square, or different color piece;
6643 click-click move is possible */
6644 if (toX == -2 || toY == -2) {
6645 boards[0][fromY][fromX] = EmptySquare;
6646 DrawPosition(FALSE, boards[currentMove]);
6648 } else if (toX >= 0 && toY >= 0) {
6649 boards[0][toY][toX] = boards[0][fromY][fromX];
6650 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6651 if(boards[0][fromY][0] != EmptySquare) {
6652 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6653 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6656 if(fromX == BOARD_RGHT+1) {
6657 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6658 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6659 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6662 boards[0][fromY][fromX] = EmptySquare;
6663 DrawPosition(FALSE, boards[currentMove]);
6669 if(toX < 0 || toY < 0) return;
6670 pdown = boards[currentMove][fromY][fromX];
6671 pup = boards[currentMove][toY][toX];
6673 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6674 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6675 if( pup != EmptySquare ) return;
6676 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6677 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6678 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6679 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6680 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6681 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6682 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6686 /* [HGM] always test for legality, to get promotion info */
6687 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6688 fromY, fromX, toY, toX, promoChar);
6690 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6692 /* [HGM] but possibly ignore an IllegalMove result */
6693 if (appData.testLegality) {
6694 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6695 DisplayMoveError(_("Illegal move"));
6700 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6701 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6702 ClearPremoveHighlights(); // was included
6703 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6707 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6710 /* Common tail of UserMoveEvent and DropMenuEvent */
6712 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6716 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6717 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6718 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6719 if(WhiteOnMove(currentMove)) {
6720 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6722 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6726 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6727 move type in caller when we know the move is a legal promotion */
6728 if(moveType == NormalMove && promoChar)
6729 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6731 /* [HGM] <popupFix> The following if has been moved here from
6732 UserMoveEvent(). Because it seemed to belong here (why not allow
6733 piece drops in training games?), and because it can only be
6734 performed after it is known to what we promote. */
6735 if (gameMode == Training) {
6736 /* compare the move played on the board to the next move in the
6737 * game. If they match, display the move and the opponent's response.
6738 * If they don't match, display an error message.
6742 CopyBoard(testBoard, boards[currentMove]);
6743 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6745 if (CompareBoards(testBoard, boards[currentMove+1])) {
6746 ForwardInner(currentMove+1);
6748 /* Autoplay the opponent's response.
6749 * if appData.animate was TRUE when Training mode was entered,
6750 * the response will be animated.
6752 saveAnimate = appData.animate;
6753 appData.animate = animateTraining;
6754 ForwardInner(currentMove+1);
6755 appData.animate = saveAnimate;
6757 /* check for the end of the game */
6758 if (currentMove >= forwardMostMove) {
6759 gameMode = PlayFromGameFile;
6761 SetTrainingModeOff();
6762 DisplayInformation(_("End of game"));
6765 DisplayError(_("Incorrect move"), 0);
6770 /* Ok, now we know that the move is good, so we can kill
6771 the previous line in Analysis Mode */
6772 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6773 && currentMove < forwardMostMove) {
6774 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6775 else forwardMostMove = currentMove;
6780 /* If we need the chess program but it's dead, restart it */
6781 ResurrectChessProgram();
6783 /* A user move restarts a paused game*/
6787 thinkOutput[0] = NULLCHAR;
6789 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6791 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6792 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6796 if (gameMode == BeginningOfGame) {
6797 if (appData.noChessProgram) {
6798 gameMode = EditGame;
6802 gameMode = MachinePlaysBlack;
6805 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6807 if (first.sendName) {
6808 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6809 SendToProgram(buf, &first);
6816 /* Relay move to ICS or chess engine */
6817 if (appData.icsActive) {
6818 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6819 gameMode == IcsExamining) {
6820 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6821 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6823 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6825 // also send plain move, in case ICS does not understand atomic claims
6826 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6830 if (first.sendTime && (gameMode == BeginningOfGame ||
6831 gameMode == MachinePlaysWhite ||
6832 gameMode == MachinePlaysBlack)) {
6833 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6835 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6836 // [HGM] book: if program might be playing, let it use book
6837 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6838 first.maybeThinking = TRUE;
6839 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6840 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6841 SendBoard(&first, currentMove+1);
6842 } else SendMoveToProgram(forwardMostMove-1, &first);
6843 if (currentMove == cmailOldMove + 1) {
6844 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6848 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6852 if(appData.testLegality)
6853 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6859 if (WhiteOnMove(currentMove)) {
6860 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6862 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6866 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6871 case MachinePlaysBlack:
6872 case MachinePlaysWhite:
6873 /* disable certain menu options while machine is thinking */
6874 SetMachineThinkingEnables();
6881 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6882 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6884 if(bookHit) { // [HGM] book: simulate book reply
6885 static char bookMove[MSG_SIZ]; // a bit generous?
6887 programStats.nodes = programStats.depth = programStats.time =
6888 programStats.score = programStats.got_only_move = 0;
6889 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6891 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6892 strcat(bookMove, bookHit);
6893 HandleMachineMove(bookMove, &first);
6899 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6901 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6902 Markers *m = (Markers *) closure;
6903 if(rf == fromY && ff == fromX)
6904 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6905 || kind == WhiteCapturesEnPassant
6906 || kind == BlackCapturesEnPassant);
6907 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6911 MarkTargetSquares (int clear)
6914 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6915 !appData.testLegality || gameMode == EditPosition) return;
6917 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6920 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6921 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6922 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6924 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6927 DrawPosition(TRUE, NULL);
6931 Explode (Board board, int fromX, int fromY, int toX, int toY)
6933 if(gameInfo.variant == VariantAtomic &&
6934 (board[toY][toX] != EmptySquare || // capture?
6935 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6936 board[fromY][fromX] == BlackPawn )
6938 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6944 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6947 CanPromote (ChessSquare piece, int y)
6949 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6950 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6951 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6952 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6953 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6954 gameInfo.variant == VariantMakruk) return FALSE;
6955 return (piece == BlackPawn && y == 1 ||
6956 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6957 piece == BlackLance && y == 1 ||
6958 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6962 LeftClick (ClickType clickType, int xPix, int yPix)
6965 Boolean saveAnimate;
6966 static int second = 0, promotionChoice = 0, clearFlag = 0;
6967 char promoChoice = NULLCHAR;
6969 static TimeMark lastClickTime, prevClickTime;
6971 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6973 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6975 if (clickType == Press) ErrorPopDown();
6977 x = EventToSquare(xPix, BOARD_WIDTH);
6978 y = EventToSquare(yPix, BOARD_HEIGHT);
6979 if (!flipView && y >= 0) {
6980 y = BOARD_HEIGHT - 1 - y;
6982 if (flipView && x >= 0) {
6983 x = BOARD_WIDTH - 1 - x;
6986 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6987 defaultPromoChoice = promoSweep;
6988 promoSweep = EmptySquare; // terminate sweep
6989 promoDefaultAltered = TRUE;
6990 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6993 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6994 if(clickType == Release) return; // ignore upclick of click-click destination
6995 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6996 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6997 if(gameInfo.holdingsWidth &&
6998 (WhiteOnMove(currentMove)
6999 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7000 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7001 // click in right holdings, for determining promotion piece
7002 ChessSquare p = boards[currentMove][y][x];
7003 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7004 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7005 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7006 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7011 DrawPosition(FALSE, boards[currentMove]);
7015 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7016 if(clickType == Press
7017 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7018 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7019 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7022 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7023 // could be static click on premove from-square: abort premove
7025 ClearPremoveHighlights();
7028 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7029 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7031 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7032 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7033 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7034 defaultPromoChoice = DefaultPromoChoice(side);
7037 autoQueen = appData.alwaysPromoteToQueen;
7041 gatingPiece = EmptySquare;
7042 if (clickType != Press) {
7043 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7044 DragPieceEnd(xPix, yPix); dragging = 0;
7045 DrawPosition(FALSE, NULL);
7049 doubleClick = FALSE;
7050 fromX = x; fromY = y; toX = toY = -1;
7051 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7052 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7053 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7055 if (OKToStartUserMove(fromX, fromY)) {
7057 MarkTargetSquares(0);
7058 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7059 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7060 promoSweep = defaultPromoChoice;
7061 selectFlag = 0; lastX = xPix; lastY = yPix;
7062 Sweep(0); // Pawn that is going to promote: preview promotion piece
7063 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7065 if (appData.highlightDragging) {
7066 SetHighlights(fromX, fromY, -1, -1);
7068 } else fromX = fromY = -1;
7074 if (clickType == Press && gameMode != EditPosition) {
7079 // ignore off-board to clicks
7080 if(y < 0 || x < 0) return;
7082 /* Check if clicking again on the same color piece */
7083 fromP = boards[currentMove][fromY][fromX];
7084 toP = boards[currentMove][y][x];
7085 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7086 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7087 WhitePawn <= toP && toP <= WhiteKing &&
7088 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7089 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7090 (BlackPawn <= fromP && fromP <= BlackKing &&
7091 BlackPawn <= toP && toP <= BlackKing &&
7092 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7093 !(fromP == BlackKing && toP == BlackRook && frc))) {
7094 /* Clicked again on same color piece -- changed his mind */
7095 second = (x == fromX && y == fromY);
7096 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7097 second = FALSE; // first double-click rather than scond click
7098 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7100 promoDefaultAltered = FALSE;
7101 MarkTargetSquares(1);
7102 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7103 if (appData.highlightDragging) {
7104 SetHighlights(x, y, -1, -1);
7108 if (OKToStartUserMove(x, y)) {
7109 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7110 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7111 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7112 gatingPiece = boards[currentMove][fromY][fromX];
7113 else gatingPiece = doubleClick ? fromP : EmptySquare;
7115 fromY = y; dragging = 1;
7116 MarkTargetSquares(0);
7117 DragPieceBegin(xPix, yPix, FALSE);
7118 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7119 promoSweep = defaultPromoChoice;
7120 selectFlag = 0; lastX = xPix; lastY = yPix;
7121 Sweep(0); // Pawn that is going to promote: preview promotion piece
7125 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7128 // ignore clicks on holdings
7129 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7132 if (clickType == Release && x == fromX && y == fromY) {
7133 DragPieceEnd(xPix, yPix); dragging = 0;
7135 // a deferred attempt to click-click move an empty square on top of a piece
7136 boards[currentMove][y][x] = EmptySquare;
7138 DrawPosition(FALSE, boards[currentMove]);
7139 fromX = fromY = -1; clearFlag = 0;
7142 if (appData.animateDragging) {
7143 /* Undo animation damage if any */
7144 DrawPosition(FALSE, NULL);
7147 /* Second up/down in same square; just abort move */
7150 gatingPiece = EmptySquare;
7153 ClearPremoveHighlights();
7155 /* First upclick in same square; start click-click mode */
7156 SetHighlights(x, y, -1, -1);
7163 /* we now have a different from- and (possibly off-board) to-square */
7164 /* Completed move */
7167 saveAnimate = appData.animate;
7168 MarkTargetSquares(1);
7169 if (clickType == Press) {
7170 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7171 // must be Edit Position mode with empty-square selected
7172 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7173 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7176 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7177 ChessSquare piece = boards[currentMove][fromY][fromX];
7178 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7179 promoSweep = defaultPromoChoice;
7180 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7181 selectFlag = 0; lastX = xPix; lastY = yPix;
7182 Sweep(0); // Pawn that is going to promote: preview promotion piece
7183 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7184 DrawPosition(FALSE, boards[currentMove]);
7187 /* Finish clickclick move */
7188 if (appData.animate || appData.highlightLastMove) {
7189 SetHighlights(fromX, fromY, toX, toY);
7194 /* Finish drag move */
7195 if (appData.highlightLastMove) {
7196 SetHighlights(fromX, fromY, toX, toY);
7200 DragPieceEnd(xPix, yPix); dragging = 0;
7201 /* Don't animate move and drag both */
7202 appData.animate = FALSE;
7205 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7206 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7207 ChessSquare piece = boards[currentMove][fromY][fromX];
7208 if(gameMode == EditPosition && piece != EmptySquare &&
7209 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7212 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7213 n = PieceToNumber(piece - (int)BlackPawn);
7214 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7215 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7216 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7218 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7219 n = PieceToNumber(piece);
7220 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7221 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7222 boards[currentMove][n][BOARD_WIDTH-2]++;
7224 boards[currentMove][fromY][fromX] = EmptySquare;
7228 DrawPosition(TRUE, boards[currentMove]);
7232 // off-board moves should not be highlighted
7233 if(x < 0 || y < 0) ClearHighlights();
7235 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7237 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7238 SetHighlights(fromX, fromY, toX, toY);
7239 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7240 // [HGM] super: promotion to captured piece selected from holdings
7241 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7242 promotionChoice = TRUE;
7243 // kludge follows to temporarily execute move on display, without promoting yet
7244 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7245 boards[currentMove][toY][toX] = p;
7246 DrawPosition(FALSE, boards[currentMove]);
7247 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7248 boards[currentMove][toY][toX] = q;
7249 DisplayMessage("Click in holdings to choose piece", "");
7254 int oldMove = currentMove;
7255 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7256 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7257 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7258 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7259 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7260 DrawPosition(TRUE, boards[currentMove]);
7263 appData.animate = saveAnimate;
7264 if (appData.animate || appData.animateDragging) {
7265 /* Undo animation damage if needed */
7266 DrawPosition(FALSE, NULL);
7271 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7272 { // front-end-free part taken out of PieceMenuPopup
7273 int whichMenu; int xSqr, ySqr;
7275 if(seekGraphUp) { // [HGM] seekgraph
7276 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7277 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7281 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7282 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7283 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7284 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7285 if(action == Press) {
7286 originalFlip = flipView;
7287 flipView = !flipView; // temporarily flip board to see game from partners perspective
7288 DrawPosition(TRUE, partnerBoard);
7289 DisplayMessage(partnerStatus, "");
7291 } else if(action == Release) {
7292 flipView = originalFlip;
7293 DrawPosition(TRUE, boards[currentMove]);
7299 xSqr = EventToSquare(x, BOARD_WIDTH);
7300 ySqr = EventToSquare(y, BOARD_HEIGHT);
7301 if (action == Release) {
7302 if(pieceSweep != EmptySquare) {
7303 EditPositionMenuEvent(pieceSweep, toX, toY);
7304 pieceSweep = EmptySquare;
7305 } else UnLoadPV(); // [HGM] pv
7307 if (action != Press) return -2; // return code to be ignored
7310 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7312 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7313 if (xSqr < 0 || ySqr < 0) return -1;
7314 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7315 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7316 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7317 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7321 if(!appData.icsEngineAnalyze) return -1;
7322 case IcsPlayingWhite:
7323 case IcsPlayingBlack:
7324 if(!appData.zippyPlay) goto noZip;
7327 case MachinePlaysWhite:
7328 case MachinePlaysBlack:
7329 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7330 if (!appData.dropMenu) {
7332 return 2; // flag front-end to grab mouse events
7334 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7335 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7338 if (xSqr < 0 || ySqr < 0) return -1;
7339 if (!appData.dropMenu || appData.testLegality &&
7340 gameInfo.variant != VariantBughouse &&
7341 gameInfo.variant != VariantCrazyhouse) return -1;
7342 whichMenu = 1; // drop menu
7348 if (((*fromX = xSqr) < 0) ||
7349 ((*fromY = ySqr) < 0)) {
7350 *fromX = *fromY = -1;
7354 *fromX = BOARD_WIDTH - 1 - *fromX;
7356 *fromY = BOARD_HEIGHT - 1 - *fromY;
7362 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7364 // char * hint = lastHint;
7365 FrontEndProgramStats stats;
7367 stats.which = cps == &first ? 0 : 1;
7368 stats.depth = cpstats->depth;
7369 stats.nodes = cpstats->nodes;
7370 stats.score = cpstats->score;
7371 stats.time = cpstats->time;
7372 stats.pv = cpstats->movelist;
7373 stats.hint = lastHint;
7374 stats.an_move_index = 0;
7375 stats.an_move_count = 0;
7377 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7378 stats.hint = cpstats->move_name;
7379 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7380 stats.an_move_count = cpstats->nr_moves;
7383 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
7385 SetProgramStats( &stats );
7389 ClearEngineOutputPane (int which)
7391 static FrontEndProgramStats dummyStats;
7392 dummyStats.which = which;
7393 dummyStats.pv = "#";
7394 SetProgramStats( &dummyStats );
7397 #define MAXPLAYERS 500
7400 TourneyStandings (int display)
7402 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7403 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7404 char result, *p, *names[MAXPLAYERS];
7406 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7407 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7408 names[0] = p = strdup(appData.participants);
7409 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7411 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7413 while(result = appData.results[nr]) {
7414 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7415 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7416 wScore = bScore = 0;
7418 case '+': wScore = 2; break;
7419 case '-': bScore = 2; break;
7420 case '=': wScore = bScore = 1; break;
7422 case '*': return strdup("busy"); // tourney not finished
7430 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7431 for(w=0; w<nPlayers; w++) {
7433 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7434 ranking[w] = b; points[w] = bScore; score[b] = -2;
7436 p = malloc(nPlayers*34+1);
7437 for(w=0; w<nPlayers && w<display; w++)
7438 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7444 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7445 { // count all piece types
7447 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7448 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7449 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7452 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7453 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7454 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7455 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7456 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7457 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7462 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7464 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7465 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7467 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7468 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7469 if(myPawns == 2 && nMine == 3) // KPP
7470 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7471 if(myPawns == 1 && nMine == 2) // KP
7472 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7473 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7474 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7475 if(myPawns) return FALSE;
7476 if(pCnt[WhiteRook+side])
7477 return pCnt[BlackRook-side] ||
7478 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7479 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7480 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7481 if(pCnt[WhiteCannon+side]) {
7482 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7483 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7485 if(pCnt[WhiteKnight+side])
7486 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7491 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7493 VariantClass v = gameInfo.variant;
7495 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7496 if(v == VariantShatranj) return TRUE; // always winnable through baring
7497 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7498 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7500 if(v == VariantXiangqi) {
7501 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7503 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7504 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7505 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7506 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7507 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7508 if(stale) // we have at least one last-rank P plus perhaps C
7509 return majors // KPKX
7510 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7512 return pCnt[WhiteFerz+side] // KCAK
7513 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7514 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7515 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7517 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7518 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7520 if(nMine == 1) return FALSE; // bare King
7521 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
7522 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7523 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7524 // by now we have King + 1 piece (or multiple Bishops on the same color)
7525 if(pCnt[WhiteKnight+side])
7526 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7527 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7528 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7530 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7531 if(pCnt[WhiteAlfil+side])
7532 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7533 if(pCnt[WhiteWazir+side])
7534 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7541 CompareWithRights (Board b1, Board b2)
7544 if(!CompareBoards(b1, b2)) return FALSE;
7545 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7546 /* compare castling rights */
7547 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7548 rights++; /* King lost rights, while rook still had them */
7549 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7550 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7551 rights++; /* but at least one rook lost them */
7553 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7555 if( b1[CASTLING][5] != NoRights ) {
7556 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7563 Adjudicate (ChessProgramState *cps)
7564 { // [HGM] some adjudications useful with buggy engines
7565 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7566 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7567 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7568 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7569 int k, count = 0; static int bare = 1;
7570 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7571 Boolean canAdjudicate = !appData.icsActive;
7573 // most tests only when we understand the game, i.e. legality-checking on
7574 if( appData.testLegality )
7575 { /* [HGM] Some more adjudications for obstinate engines */
7576 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7577 static int moveCount = 6;
7579 char *reason = NULL;
7581 /* Count what is on board. */
7582 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7584 /* Some material-based adjudications that have to be made before stalemate test */
7585 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7586 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7587 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7588 if(canAdjudicate && appData.checkMates) {
7590 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7591 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7592 "Xboard adjudication: King destroyed", GE_XBOARD );
7597 /* Bare King in Shatranj (loses) or Losers (wins) */
7598 if( nrW == 1 || nrB == 1) {
7599 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7600 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7601 if(canAdjudicate && appData.checkMates) {
7603 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7604 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7605 "Xboard adjudication: Bare king", GE_XBOARD );
7609 if( gameInfo.variant == VariantShatranj && --bare < 0)
7611 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7612 if(canAdjudicate && appData.checkMates) {
7613 /* but only adjudicate if adjudication enabled */
7615 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7616 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7617 "Xboard adjudication: Bare king", GE_XBOARD );
7624 // don't wait for engine to announce game end if we can judge ourselves
7625 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7627 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7628 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7629 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7630 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7633 reason = "Xboard adjudication: 3rd check";
7634 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7644 reason = "Xboard adjudication: Stalemate";
7645 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7646 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7647 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7648 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7649 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7650 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7651 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7652 EP_CHECKMATE : EP_WINS);
7653 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7654 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7658 reason = "Xboard adjudication: Checkmate";
7659 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7663 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7665 result = GameIsDrawn; break;
7667 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7669 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7673 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7675 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7676 GameEnds( result, reason, GE_XBOARD );
7680 /* Next absolutely insufficient mating material. */
7681 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7682 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7683 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7685 /* always flag draws, for judging claims */
7686 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7688 if(canAdjudicate && appData.materialDraws) {
7689 /* but only adjudicate them if adjudication enabled */
7690 if(engineOpponent) {
7691 SendToProgram("force\n", engineOpponent); // suppress reply
7692 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7694 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7699 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7700 if(gameInfo.variant == VariantXiangqi ?
7701 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7703 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7704 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7705 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7706 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7708 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7709 { /* if the first 3 moves do not show a tactical win, declare draw */
7710 if(engineOpponent) {
7711 SendToProgram("force\n", engineOpponent); // suppress reply
7712 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7714 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7717 } else moveCount = 6;
7720 // Repetition draws and 50-move rule can be applied independently of legality testing
7722 /* Check for rep-draws */
7724 for(k = forwardMostMove-2;
7725 k>=backwardMostMove && k>=forwardMostMove-100 &&
7726 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7727 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7730 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7731 /* compare castling rights */
7732 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7733 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7734 rights++; /* King lost rights, while rook still had them */
7735 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7736 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7737 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7738 rights++; /* but at least one rook lost them */
7740 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7741 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7743 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7744 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7745 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7748 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7749 && appData.drawRepeats > 1) {
7750 /* adjudicate after user-specified nr of repeats */
7751 int result = GameIsDrawn;
7752 char *details = "XBoard adjudication: repetition draw";
7753 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7754 // [HGM] xiangqi: check for forbidden perpetuals
7755 int m, ourPerpetual = 1, hisPerpetual = 1;
7756 for(m=forwardMostMove; m>k; m-=2) {
7757 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7758 ourPerpetual = 0; // the current mover did not always check
7759 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7760 hisPerpetual = 0; // the opponent did not always check
7762 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7763 ourPerpetual, hisPerpetual);
7764 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7765 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7766 details = "Xboard adjudication: perpetual checking";
7768 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7769 break; // (or we would have caught him before). Abort repetition-checking loop.
7771 // Now check for perpetual chases
7772 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7773 hisPerpetual = PerpetualChase(k, forwardMostMove);
7774 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7775 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7776 static char resdet[MSG_SIZ];
7777 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7779 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7781 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7782 break; // Abort repetition-checking loop.
7784 // if neither of us is checking or chasing all the time, or both are, it is draw
7786 if(engineOpponent) {
7787 SendToProgram("force\n", engineOpponent); // suppress reply
7788 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7790 GameEnds( result, details, GE_XBOARD );
7793 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7794 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7798 /* Now we test for 50-move draws. Determine ply count */
7799 count = forwardMostMove;
7800 /* look for last irreversble move */
7801 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7803 /* if we hit starting position, add initial plies */
7804 if( count == backwardMostMove )
7805 count -= initialRulePlies;
7806 count = forwardMostMove - count;
7807 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7808 // adjust reversible move counter for checks in Xiangqi
7809 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7810 if(i < backwardMostMove) i = backwardMostMove;
7811 while(i <= forwardMostMove) {
7812 lastCheck = inCheck; // check evasion does not count
7813 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7814 if(inCheck || lastCheck) count--; // check does not count
7819 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7820 /* this is used to judge if draw claims are legal */
7821 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7822 if(engineOpponent) {
7823 SendToProgram("force\n", engineOpponent); // suppress reply
7824 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7826 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7830 /* if draw offer is pending, treat it as a draw claim
7831 * when draw condition present, to allow engines a way to
7832 * claim draws before making their move to avoid a race
7833 * condition occurring after their move
7835 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7837 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7838 p = "Draw claim: 50-move rule";
7839 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7840 p = "Draw claim: 3-fold repetition";
7841 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7842 p = "Draw claim: insufficient mating material";
7843 if( p != NULL && canAdjudicate) {
7844 if(engineOpponent) {
7845 SendToProgram("force\n", engineOpponent); // suppress reply
7846 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7848 GameEnds( GameIsDrawn, p, GE_XBOARD );
7853 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7854 if(engineOpponent) {
7855 SendToProgram("force\n", engineOpponent); // suppress reply
7856 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7858 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7865 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7866 { // [HGM] book: this routine intercepts moves to simulate book replies
7867 char *bookHit = NULL;
7869 //first determine if the incoming move brings opponent into his book
7870 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7871 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7872 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7873 if(bookHit != NULL && !cps->bookSuspend) {
7874 // make sure opponent is not going to reply after receiving move to book position
7875 SendToProgram("force\n", cps);
7876 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7878 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7879 // now arrange restart after book miss
7881 // after a book hit we never send 'go', and the code after the call to this routine
7882 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7883 char buf[MSG_SIZ], *move = bookHit;
7885 int fromX, fromY, toX, toY;
7889 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7890 &fromX, &fromY, &toX, &toY, &promoChar)) {
7891 (void) CoordsToAlgebraic(boards[forwardMostMove],
7892 PosFlags(forwardMostMove),
7893 fromY, fromX, toY, toX, promoChar, move);
7895 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7899 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7900 SendToProgram(buf, cps);
7901 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7902 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7903 SendToProgram("go\n", cps);
7904 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7905 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7906 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7907 SendToProgram("go\n", cps);
7908 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7910 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7914 ChessProgramState *savedState;
7916 DeferredBookMove (void)
7918 if(savedState->lastPing != savedState->lastPong)
7919 ScheduleDelayedEvent(DeferredBookMove, 10);
7921 HandleMachineMove(savedMessage, savedState);
7924 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7927 HandleMachineMove (char *message, ChessProgramState *cps)
7929 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7930 char realname[MSG_SIZ];
7931 int fromX, fromY, toX, toY;
7938 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7939 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7940 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7941 DisplayError(_("Invalid pairing from pairing engine"), 0);
7944 pairingReceived = 1;
7946 return; // Skim the pairing messages here.
7951 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7953 * Kludge to ignore BEL characters
7955 while (*message == '\007') message++;
7958 * [HGM] engine debug message: ignore lines starting with '#' character
7960 if(cps->debug && *message == '#') return;
7963 * Look for book output
7965 if (cps == &first && bookRequested) {
7966 if (message[0] == '\t' || message[0] == ' ') {
7967 /* Part of the book output is here; append it */
7968 strcat(bookOutput, message);
7969 strcat(bookOutput, " \n");
7971 } else if (bookOutput[0] != NULLCHAR) {
7972 /* All of book output has arrived; display it */
7973 char *p = bookOutput;
7974 while (*p != NULLCHAR) {
7975 if (*p == '\t') *p = ' ';
7978 DisplayInformation(bookOutput);
7979 bookRequested = FALSE;
7980 /* Fall through to parse the current output */
7985 * Look for machine move.
7987 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7988 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7990 /* This method is only useful on engines that support ping */
7991 if (cps->lastPing != cps->lastPong) {
7992 if (gameMode == BeginningOfGame) {
7993 /* Extra move from before last new; ignore */
7994 if (appData.debugMode) {
7995 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7998 if (appData.debugMode) {
7999 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8000 cps->which, gameMode);
8003 SendToProgram("undo\n", cps);
8009 case BeginningOfGame:
8010 /* Extra move from before last reset; ignore */
8011 if (appData.debugMode) {
8012 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8019 /* Extra move after we tried to stop. The mode test is
8020 not a reliable way of detecting this problem, but it's
8021 the best we can do on engines that don't support ping.
8023 if (appData.debugMode) {
8024 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8025 cps->which, gameMode);
8027 SendToProgram("undo\n", cps);
8030 case MachinePlaysWhite:
8031 case IcsPlayingWhite:
8032 machineWhite = TRUE;
8035 case MachinePlaysBlack:
8036 case IcsPlayingBlack:
8037 machineWhite = FALSE;
8040 case TwoMachinesPlay:
8041 machineWhite = (cps->twoMachinesColor[0] == 'w');
8044 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8045 if (appData.debugMode) {
8047 "Ignoring move out of turn by %s, gameMode %d"
8048 ", forwardMost %d\n",
8049 cps->which, gameMode, forwardMostMove);
8054 if(cps->alphaRank) AlphaRank(machineMove, 4);
8055 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8056 &fromX, &fromY, &toX, &toY, &promoChar)) {
8057 /* Machine move could not be parsed; ignore it. */
8058 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8059 machineMove, _(cps->which));
8060 DisplayError(buf1, 0);
8061 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8062 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8063 if (gameMode == TwoMachinesPlay) {
8064 GameEnds(machineWhite ? BlackWins : WhiteWins,
8070 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8071 /* So we have to redo legality test with true e.p. status here, */
8072 /* to make sure an illegal e.p. capture does not slip through, */
8073 /* to cause a forfeit on a justified illegal-move complaint */
8074 /* of the opponent. */
8075 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8077 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8078 fromY, fromX, toY, toX, promoChar);
8079 if(moveType == IllegalMove) {
8080 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8081 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8082 GameEnds(machineWhite ? BlackWins : WhiteWins,
8085 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8086 /* [HGM] Kludge to handle engines that send FRC-style castling
8087 when they shouldn't (like TSCP-Gothic) */
8089 case WhiteASideCastleFR:
8090 case BlackASideCastleFR:
8092 currentMoveString[2]++;
8094 case WhiteHSideCastleFR:
8095 case BlackHSideCastleFR:
8097 currentMoveString[2]--;
8099 default: ; // nothing to do, but suppresses warning of pedantic compilers
8102 hintRequested = FALSE;
8103 lastHint[0] = NULLCHAR;
8104 bookRequested = FALSE;
8105 /* Program may be pondering now */
8106 cps->maybeThinking = TRUE;
8107 if (cps->sendTime == 2) cps->sendTime = 1;
8108 if (cps->offeredDraw) cps->offeredDraw--;
8110 /* [AS] Save move info*/
8111 pvInfoList[ forwardMostMove ].score = programStats.score;
8112 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8113 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8115 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8117 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8118 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8121 while( count < adjudicateLossPlies ) {
8122 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8125 score = -score; /* Flip score for winning side */
8128 if( score > adjudicateLossThreshold ) {
8135 if( count >= adjudicateLossPlies ) {
8136 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8138 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8139 "Xboard adjudication",
8146 if(Adjudicate(cps)) {
8147 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8148 return; // [HGM] adjudicate: for all automatic game ends
8152 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8154 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8155 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8157 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8159 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8161 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8162 char buf[3*MSG_SIZ];
8164 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8165 programStats.score / 100.,
8167 programStats.time / 100.,
8168 (unsigned int)programStats.nodes,
8169 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8170 programStats.movelist);
8172 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8177 /* [AS] Clear stats for next move */
8178 ClearProgramStats();
8179 thinkOutput[0] = NULLCHAR;
8180 hiddenThinkOutputState = 0;
8183 if (gameMode == TwoMachinesPlay) {
8184 /* [HGM] relaying draw offers moved to after reception of move */
8185 /* and interpreting offer as claim if it brings draw condition */
8186 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8187 SendToProgram("draw\n", cps->other);
8189 if (cps->other->sendTime) {
8190 SendTimeRemaining(cps->other,
8191 cps->other->twoMachinesColor[0] == 'w');
8193 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8194 if (firstMove && !bookHit) {
8196 if (cps->other->useColors) {
8197 SendToProgram(cps->other->twoMachinesColor, cps->other);
8199 SendToProgram("go\n", cps->other);
8201 cps->other->maybeThinking = TRUE;
8204 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8206 if (!pausing && appData.ringBellAfterMoves) {
8211 * Reenable menu items that were disabled while
8212 * machine was thinking
8214 if (gameMode != TwoMachinesPlay)
8215 SetUserThinkingEnables();
8217 // [HGM] book: after book hit opponent has received move and is now in force mode
8218 // force the book reply into it, and then fake that it outputted this move by jumping
8219 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8221 static char bookMove[MSG_SIZ]; // a bit generous?
8223 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8224 strcat(bookMove, bookHit);
8227 programStats.nodes = programStats.depth = programStats.time =
8228 programStats.score = programStats.got_only_move = 0;
8229 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8231 if(cps->lastPing != cps->lastPong) {
8232 savedMessage = message; // args for deferred call
8234 ScheduleDelayedEvent(DeferredBookMove, 10);
8243 /* Set special modes for chess engines. Later something general
8244 * could be added here; for now there is just one kludge feature,
8245 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8246 * when "xboard" is given as an interactive command.
8248 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8249 cps->useSigint = FALSE;
8250 cps->useSigterm = FALSE;
8252 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8253 ParseFeatures(message+8, cps);
8254 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8257 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8258 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8259 int dummy, s=6; char buf[MSG_SIZ];
8260 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8261 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8262 if(startedFromSetupPosition) return;
8263 ParseFEN(boards[0], &dummy, message+s);
8264 DrawPosition(TRUE, boards[0]);
8265 startedFromSetupPosition = TRUE;
8268 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8269 * want this, I was asked to put it in, and obliged.
8271 if (!strncmp(message, "setboard ", 9)) {
8272 Board initial_position;
8274 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8276 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8277 DisplayError(_("Bad FEN received from engine"), 0);
8281 CopyBoard(boards[0], initial_position);
8282 initialRulePlies = FENrulePlies;
8283 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8284 else gameMode = MachinePlaysBlack;
8285 DrawPosition(FALSE, boards[currentMove]);
8291 * Look for communication commands
8293 if (!strncmp(message, "telluser ", 9)) {
8294 if(message[9] == '\\' && message[10] == '\\')
8295 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8297 DisplayNote(message + 9);
8300 if (!strncmp(message, "tellusererror ", 14)) {
8302 if(message[14] == '\\' && message[15] == '\\')
8303 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8305 DisplayError(message + 14, 0);
8308 if (!strncmp(message, "tellopponent ", 13)) {
8309 if (appData.icsActive) {
8311 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8315 DisplayNote(message + 13);
8319 if (!strncmp(message, "tellothers ", 11)) {
8320 if (appData.icsActive) {
8322 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8328 if (!strncmp(message, "tellall ", 8)) {
8329 if (appData.icsActive) {
8331 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8335 DisplayNote(message + 8);
8339 if (strncmp(message, "warning", 7) == 0) {
8340 /* Undocumented feature, use tellusererror in new code */
8341 DisplayError(message, 0);
8344 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8345 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8346 strcat(realname, " query");
8347 AskQuestion(realname, buf2, buf1, cps->pr);
8350 /* Commands from the engine directly to ICS. We don't allow these to be
8351 * sent until we are logged on. Crafty kibitzes have been known to
8352 * interfere with the login process.
8355 if (!strncmp(message, "tellics ", 8)) {
8356 SendToICS(message + 8);
8360 if (!strncmp(message, "tellicsnoalias ", 15)) {
8361 SendToICS(ics_prefix);
8362 SendToICS(message + 15);
8366 /* The following are for backward compatibility only */
8367 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8368 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8369 SendToICS(ics_prefix);
8375 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8379 * If the move is illegal, cancel it and redraw the board.
8380 * Also deal with other error cases. Matching is rather loose
8381 * here to accommodate engines written before the spec.
8383 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8384 strncmp(message, "Error", 5) == 0) {
8385 if (StrStr(message, "name") ||
8386 StrStr(message, "rating") || StrStr(message, "?") ||
8387 StrStr(message, "result") || StrStr(message, "board") ||
8388 StrStr(message, "bk") || StrStr(message, "computer") ||
8389 StrStr(message, "variant") || StrStr(message, "hint") ||
8390 StrStr(message, "random") || StrStr(message, "depth") ||
8391 StrStr(message, "accepted")) {
8394 if (StrStr(message, "protover")) {
8395 /* Program is responding to input, so it's apparently done
8396 initializing, and this error message indicates it is
8397 protocol version 1. So we don't need to wait any longer
8398 for it to initialize and send feature commands. */
8399 FeatureDone(cps, 1);
8400 cps->protocolVersion = 1;
8403 cps->maybeThinking = FALSE;
8405 if (StrStr(message, "draw")) {
8406 /* Program doesn't have "draw" command */
8407 cps->sendDrawOffers = 0;
8410 if (cps->sendTime != 1 &&
8411 (StrStr(message, "time") || StrStr(message, "otim"))) {
8412 /* Program apparently doesn't have "time" or "otim" command */
8416 if (StrStr(message, "analyze")) {
8417 cps->analysisSupport = FALSE;
8418 cps->analyzing = FALSE;
8419 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8420 EditGameEvent(); // [HGM] try to preserve loaded game
8421 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8422 DisplayError(buf2, 0);
8425 if (StrStr(message, "(no matching move)st")) {
8426 /* Special kludge for GNU Chess 4 only */
8427 cps->stKludge = TRUE;
8428 SendTimeControl(cps, movesPerSession, timeControl,
8429 timeIncrement, appData.searchDepth,
8433 if (StrStr(message, "(no matching move)sd")) {
8434 /* Special kludge for GNU Chess 4 only */
8435 cps->sdKludge = TRUE;
8436 SendTimeControl(cps, movesPerSession, timeControl,
8437 timeIncrement, appData.searchDepth,
8441 if (!StrStr(message, "llegal")) {
8444 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8445 gameMode == IcsIdle) return;
8446 if (forwardMostMove <= backwardMostMove) return;
8447 if (pausing) PauseEvent();
8448 if(appData.forceIllegal) {
8449 // [HGM] illegal: machine refused move; force position after move into it
8450 SendToProgram("force\n", cps);
8451 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8452 // we have a real problem now, as SendBoard will use the a2a3 kludge
8453 // when black is to move, while there might be nothing on a2 or black
8454 // might already have the move. So send the board as if white has the move.
8455 // But first we must change the stm of the engine, as it refused the last move
8456 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8457 if(WhiteOnMove(forwardMostMove)) {
8458 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8459 SendBoard(cps, forwardMostMove); // kludgeless board
8461 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8462 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8463 SendBoard(cps, forwardMostMove+1); // kludgeless board
8465 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8466 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8467 gameMode == TwoMachinesPlay)
8468 SendToProgram("go\n", cps);
8471 if (gameMode == PlayFromGameFile) {
8472 /* Stop reading this game file */
8473 gameMode = EditGame;
8476 /* [HGM] illegal-move claim should forfeit game when Xboard */
8477 /* only passes fully legal moves */
8478 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8479 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8480 "False illegal-move claim", GE_XBOARD );
8481 return; // do not take back move we tested as valid
8483 currentMove = forwardMostMove-1;
8484 DisplayMove(currentMove-1); /* before DisplayMoveError */
8485 SwitchClocks(forwardMostMove-1); // [HGM] race
8486 DisplayBothClocks();
8487 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8488 parseList[currentMove], _(cps->which));
8489 DisplayMoveError(buf1);
8490 DrawPosition(FALSE, boards[currentMove]);
8492 SetUserThinkingEnables();
8495 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8496 /* Program has a broken "time" command that
8497 outputs a string not ending in newline.
8503 * If chess program startup fails, exit with an error message.
8504 * Attempts to recover here are futile. [HGM] Well, we try anyway
8506 if ((StrStr(message, "unknown host") != NULL)
8507 || (StrStr(message, "No remote directory") != NULL)
8508 || (StrStr(message, "not found") != NULL)
8509 || (StrStr(message, "No such file") != NULL)
8510 || (StrStr(message, "can't alloc") != NULL)
8511 || (StrStr(message, "Permission denied") != NULL)) {
8513 cps->maybeThinking = FALSE;
8514 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8515 _(cps->which), cps->program, cps->host, message);
8516 RemoveInputSource(cps->isr);
8517 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8519 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8522 appData.noChessProgram = TRUE;
8523 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8524 gameMode = BeginningOfGame; ModeHighlight();
8527 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8528 DisplayMessage("", ""); // erase waiting message
8529 DisplayError(buf1, 0);
8535 * Look for hint output
8537 if (sscanf(message, "Hint: %s", buf1) == 1) {
8538 if (cps == &first && hintRequested) {
8539 hintRequested = FALSE;
8540 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8541 &fromX, &fromY, &toX, &toY, &promoChar)) {
8542 (void) CoordsToAlgebraic(boards[forwardMostMove],
8543 PosFlags(forwardMostMove),
8544 fromY, fromX, toY, toX, promoChar, buf1);
8545 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8546 DisplayInformation(buf2);
8548 /* Hint move could not be parsed!? */
8549 snprintf(buf2, sizeof(buf2),
8550 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8551 buf1, _(cps->which));
8552 DisplayError(buf2, 0);
8555 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8561 * Ignore other messages if game is not in progress
8563 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8564 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8567 * look for win, lose, draw, or draw offer
8569 if (strncmp(message, "1-0", 3) == 0) {
8570 char *p, *q, *r = "";
8571 p = strchr(message, '{');
8579 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8581 } else if (strncmp(message, "0-1", 3) == 0) {
8582 char *p, *q, *r = "";
8583 p = strchr(message, '{');
8591 /* Kludge for Arasan 4.1 bug */
8592 if (strcmp(r, "Black resigns") == 0) {
8593 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8596 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8598 } else if (strncmp(message, "1/2", 3) == 0) {
8599 char *p, *q, *r = "";
8600 p = strchr(message, '{');
8609 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8612 } else if (strncmp(message, "White resign", 12) == 0) {
8613 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8615 } else if (strncmp(message, "Black resign", 12) == 0) {
8616 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8618 } else if (strncmp(message, "White matches", 13) == 0 ||
8619 strncmp(message, "Black matches", 13) == 0 ) {
8620 /* [HGM] ignore GNUShogi noises */
8622 } else if (strncmp(message, "White", 5) == 0 &&
8623 message[5] != '(' &&
8624 StrStr(message, "Black") == NULL) {
8625 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8627 } else if (strncmp(message, "Black", 5) == 0 &&
8628 message[5] != '(') {
8629 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8631 } else if (strcmp(message, "resign") == 0 ||
8632 strcmp(message, "computer resigns") == 0) {
8634 case MachinePlaysBlack:
8635 case IcsPlayingBlack:
8636 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8638 case MachinePlaysWhite:
8639 case IcsPlayingWhite:
8640 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8642 case TwoMachinesPlay:
8643 if (cps->twoMachinesColor[0] == 'w')
8644 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8646 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8653 } else if (strncmp(message, "opponent mates", 14) == 0) {
8655 case MachinePlaysBlack:
8656 case IcsPlayingBlack:
8657 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8659 case MachinePlaysWhite:
8660 case IcsPlayingWhite:
8661 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8663 case TwoMachinesPlay:
8664 if (cps->twoMachinesColor[0] == 'w')
8665 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8667 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8674 } else if (strncmp(message, "computer mates", 14) == 0) {
8676 case MachinePlaysBlack:
8677 case IcsPlayingBlack:
8678 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8680 case MachinePlaysWhite:
8681 case IcsPlayingWhite:
8682 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8684 case TwoMachinesPlay:
8685 if (cps->twoMachinesColor[0] == 'w')
8686 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8688 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8695 } else if (strncmp(message, "checkmate", 9) == 0) {
8696 if (WhiteOnMove(forwardMostMove)) {
8697 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8699 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8702 } else if (strstr(message, "Draw") != NULL ||
8703 strstr(message, "game is a draw") != NULL) {
8704 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8706 } else if (strstr(message, "offer") != NULL &&
8707 strstr(message, "draw") != NULL) {
8709 if (appData.zippyPlay && first.initDone) {
8710 /* Relay offer to ICS */
8711 SendToICS(ics_prefix);
8712 SendToICS("draw\n");
8715 cps->offeredDraw = 2; /* valid until this engine moves twice */
8716 if (gameMode == TwoMachinesPlay) {
8717 if (cps->other->offeredDraw) {
8718 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8719 /* [HGM] in two-machine mode we delay relaying draw offer */
8720 /* until after we also have move, to see if it is really claim */
8722 } else if (gameMode == MachinePlaysWhite ||
8723 gameMode == MachinePlaysBlack) {
8724 if (userOfferedDraw) {
8725 DisplayInformation(_("Machine accepts your draw offer"));
8726 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8728 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8735 * Look for thinking output
8737 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8738 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8740 int plylev, mvleft, mvtot, curscore, time;
8741 char mvname[MOVE_LEN];
8745 int prefixHint = FALSE;
8746 mvname[0] = NULLCHAR;
8749 case MachinePlaysBlack:
8750 case IcsPlayingBlack:
8751 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8753 case MachinePlaysWhite:
8754 case IcsPlayingWhite:
8755 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8760 case IcsObserving: /* [DM] icsEngineAnalyze */
8761 if (!appData.icsEngineAnalyze) ignore = TRUE;
8763 case TwoMachinesPlay:
8764 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8774 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8776 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8777 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8779 if (plyext != ' ' && plyext != '\t') {
8783 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8784 if( cps->scoreIsAbsolute &&
8785 ( gameMode == MachinePlaysBlack ||
8786 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8787 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8788 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8789 !WhiteOnMove(currentMove)
8792 curscore = -curscore;
8795 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8797 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8800 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8801 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8802 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8803 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8804 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8805 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8807 } else DisplayError(_("failed writing PV"), 0);
8810 tempStats.depth = plylev;
8811 tempStats.nodes = nodes;
8812 tempStats.time = time;
8813 tempStats.score = curscore;
8814 tempStats.got_only_move = 0;
8816 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8819 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8820 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8821 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8822 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8823 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8824 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8825 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8826 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8829 /* Buffer overflow protection */
8830 if (pv[0] != NULLCHAR) {
8831 if (strlen(pv) >= sizeof(tempStats.movelist)
8832 && appData.debugMode) {
8834 "PV is too long; using the first %u bytes.\n",
8835 (unsigned) sizeof(tempStats.movelist) - 1);
8838 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8840 sprintf(tempStats.movelist, " no PV\n");
8843 if (tempStats.seen_stat) {
8844 tempStats.ok_to_send = 1;
8847 if (strchr(tempStats.movelist, '(') != NULL) {
8848 tempStats.line_is_book = 1;
8849 tempStats.nr_moves = 0;
8850 tempStats.moves_left = 0;
8852 tempStats.line_is_book = 0;
8855 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8856 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8858 SendProgramStatsToFrontend( cps, &tempStats );
8861 [AS] Protect the thinkOutput buffer from overflow... this
8862 is only useful if buf1 hasn't overflowed first!
8864 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8866 (gameMode == TwoMachinesPlay ?
8867 ToUpper(cps->twoMachinesColor[0]) : ' '),
8868 ((double) curscore) / 100.0,
8869 prefixHint ? lastHint : "",
8870 prefixHint ? " " : "" );
8872 if( buf1[0] != NULLCHAR ) {
8873 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8875 if( strlen(pv) > max_len ) {
8876 if( appData.debugMode) {
8877 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8879 pv[max_len+1] = '\0';
8882 strcat( thinkOutput, pv);
8885 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8886 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8887 DisplayMove(currentMove - 1);
8891 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8892 /* crafty (9.25+) says "(only move) <move>"
8893 * if there is only 1 legal move
8895 sscanf(p, "(only move) %s", buf1);
8896 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8897 sprintf(programStats.movelist, "%s (only move)", buf1);
8898 programStats.depth = 1;
8899 programStats.nr_moves = 1;
8900 programStats.moves_left = 1;
8901 programStats.nodes = 1;
8902 programStats.time = 1;
8903 programStats.got_only_move = 1;
8905 /* Not really, but we also use this member to
8906 mean "line isn't going to change" (Crafty
8907 isn't searching, so stats won't change) */
8908 programStats.line_is_book = 1;
8910 SendProgramStatsToFrontend( cps, &programStats );
8912 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8913 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8914 DisplayMove(currentMove - 1);
8917 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8918 &time, &nodes, &plylev, &mvleft,
8919 &mvtot, mvname) >= 5) {
8920 /* The stat01: line is from Crafty (9.29+) in response
8921 to the "." command */
8922 programStats.seen_stat = 1;
8923 cps->maybeThinking = TRUE;
8925 if (programStats.got_only_move || !appData.periodicUpdates)
8928 programStats.depth = plylev;
8929 programStats.time = time;
8930 programStats.nodes = nodes;
8931 programStats.moves_left = mvleft;
8932 programStats.nr_moves = mvtot;
8933 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8934 programStats.ok_to_send = 1;
8935 programStats.movelist[0] = '\0';
8937 SendProgramStatsToFrontend( cps, &programStats );
8941 } else if (strncmp(message,"++",2) == 0) {
8942 /* Crafty 9.29+ outputs this */
8943 programStats.got_fail = 2;
8946 } else if (strncmp(message,"--",2) == 0) {
8947 /* Crafty 9.29+ outputs this */
8948 programStats.got_fail = 1;
8951 } else if (thinkOutput[0] != NULLCHAR &&
8952 strncmp(message, " ", 4) == 0) {
8953 unsigned message_len;
8956 while (*p && *p == ' ') p++;
8958 message_len = strlen( p );
8960 /* [AS] Avoid buffer overflow */
8961 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8962 strcat(thinkOutput, " ");
8963 strcat(thinkOutput, p);
8966 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8967 strcat(programStats.movelist, " ");
8968 strcat(programStats.movelist, p);
8971 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8972 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8973 DisplayMove(currentMove - 1);
8981 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8982 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8984 ChessProgramStats cpstats;
8986 if (plyext != ' ' && plyext != '\t') {
8990 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8991 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8992 curscore = -curscore;
8995 cpstats.depth = plylev;
8996 cpstats.nodes = nodes;
8997 cpstats.time = time;
8998 cpstats.score = curscore;
8999 cpstats.got_only_move = 0;
9000 cpstats.movelist[0] = '\0';
9002 if (buf1[0] != NULLCHAR) {
9003 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9006 cpstats.ok_to_send = 0;
9007 cpstats.line_is_book = 0;
9008 cpstats.nr_moves = 0;
9009 cpstats.moves_left = 0;
9011 SendProgramStatsToFrontend( cps, &cpstats );
9018 /* Parse a game score from the character string "game", and
9019 record it as the history of the current game. The game
9020 score is NOT assumed to start from the standard position.
9021 The display is not updated in any way.
9024 ParseGameHistory (char *game)
9027 int fromX, fromY, toX, toY, boardIndex;
9032 if (appData.debugMode)
9033 fprintf(debugFP, "Parsing game history: %s\n", game);
9035 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9036 gameInfo.site = StrSave(appData.icsHost);
9037 gameInfo.date = PGNDate();
9038 gameInfo.round = StrSave("-");
9040 /* Parse out names of players */
9041 while (*game == ' ') game++;
9043 while (*game != ' ') *p++ = *game++;
9045 gameInfo.white = StrSave(buf);
9046 while (*game == ' ') game++;
9048 while (*game != ' ' && *game != '\n') *p++ = *game++;
9050 gameInfo.black = StrSave(buf);
9053 boardIndex = blackPlaysFirst ? 1 : 0;
9056 yyboardindex = boardIndex;
9057 moveType = (ChessMove) Myylex();
9059 case IllegalMove: /* maybe suicide chess, etc. */
9060 if (appData.debugMode) {
9061 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9062 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9063 setbuf(debugFP, NULL);
9065 case WhitePromotion:
9066 case BlackPromotion:
9067 case WhiteNonPromotion:
9068 case BlackNonPromotion:
9070 case WhiteCapturesEnPassant:
9071 case BlackCapturesEnPassant:
9072 case WhiteKingSideCastle:
9073 case WhiteQueenSideCastle:
9074 case BlackKingSideCastle:
9075 case BlackQueenSideCastle:
9076 case WhiteKingSideCastleWild:
9077 case WhiteQueenSideCastleWild:
9078 case BlackKingSideCastleWild:
9079 case BlackQueenSideCastleWild:
9081 case WhiteHSideCastleFR:
9082 case WhiteASideCastleFR:
9083 case BlackHSideCastleFR:
9084 case BlackASideCastleFR:
9086 fromX = currentMoveString[0] - AAA;
9087 fromY = currentMoveString[1] - ONE;
9088 toX = currentMoveString[2] - AAA;
9089 toY = currentMoveString[3] - ONE;
9090 promoChar = currentMoveString[4];
9094 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9095 fromX = moveType == WhiteDrop ?
9096 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9097 (int) CharToPiece(ToLower(currentMoveString[0]));
9099 toX = currentMoveString[2] - AAA;
9100 toY = currentMoveString[3] - ONE;
9101 promoChar = NULLCHAR;
9105 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9106 if (appData.debugMode) {
9107 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9108 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9109 setbuf(debugFP, NULL);
9111 DisplayError(buf, 0);
9113 case ImpossibleMove:
9115 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9116 if (appData.debugMode) {
9117 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9118 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9119 setbuf(debugFP, NULL);
9121 DisplayError(buf, 0);
9124 if (boardIndex < backwardMostMove) {
9125 /* Oops, gap. How did that happen? */
9126 DisplayError(_("Gap in move list"), 0);
9129 backwardMostMove = blackPlaysFirst ? 1 : 0;
9130 if (boardIndex > forwardMostMove) {
9131 forwardMostMove = boardIndex;
9135 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9136 strcat(parseList[boardIndex-1], " ");
9137 strcat(parseList[boardIndex-1], yy_text);
9149 case GameUnfinished:
9150 if (gameMode == IcsExamining) {
9151 if (boardIndex < backwardMostMove) {
9152 /* Oops, gap. How did that happen? */
9155 backwardMostMove = blackPlaysFirst ? 1 : 0;
9158 gameInfo.result = moveType;
9159 p = strchr(yy_text, '{');
9160 if (p == NULL) p = strchr(yy_text, '(');
9163 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9165 q = strchr(p, *p == '{' ? '}' : ')');
9166 if (q != NULL) *q = NULLCHAR;
9169 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9170 gameInfo.resultDetails = StrSave(p);
9173 if (boardIndex >= forwardMostMove &&
9174 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9175 backwardMostMove = blackPlaysFirst ? 1 : 0;
9178 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9179 fromY, fromX, toY, toX, promoChar,
9180 parseList[boardIndex]);
9181 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9182 /* currentMoveString is set as a side-effect of yylex */
9183 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9184 strcat(moveList[boardIndex], "\n");
9186 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9187 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9193 if(gameInfo.variant != VariantShogi)
9194 strcat(parseList[boardIndex - 1], "+");
9198 strcat(parseList[boardIndex - 1], "#");
9205 /* Apply a move to the given board */
9207 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9209 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9210 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9212 /* [HGM] compute & store e.p. status and castling rights for new position */
9213 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9215 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9216 oldEP = (signed char)board[EP_STATUS];
9217 board[EP_STATUS] = EP_NONE;
9219 if (fromY == DROP_RANK) {
9221 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9222 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9225 piece = board[toY][toX] = (ChessSquare) fromX;
9229 if( board[toY][toX] != EmptySquare )
9230 board[EP_STATUS] = EP_CAPTURE;
9232 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9233 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9234 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9236 if( board[fromY][fromX] == WhitePawn ) {
9237 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9238 board[EP_STATUS] = EP_PAWN_MOVE;
9240 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9241 gameInfo.variant != VariantBerolina || toX < fromX)
9242 board[EP_STATUS] = toX | berolina;
9243 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9244 gameInfo.variant != VariantBerolina || toX > fromX)
9245 board[EP_STATUS] = toX;
9248 if( board[fromY][fromX] == BlackPawn ) {
9249 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9250 board[EP_STATUS] = EP_PAWN_MOVE;
9251 if( toY-fromY== -2) {
9252 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9253 gameInfo.variant != VariantBerolina || toX < fromX)
9254 board[EP_STATUS] = toX | berolina;
9255 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9256 gameInfo.variant != VariantBerolina || toX > fromX)
9257 board[EP_STATUS] = toX;
9261 for(i=0; i<nrCastlingRights; i++) {
9262 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9263 board[CASTLING][i] == toX && castlingRank[i] == toY
9264 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9267 if (fromX == toX && fromY == toY) return;
9269 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9270 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9271 if(gameInfo.variant == VariantKnightmate)
9272 king += (int) WhiteUnicorn - (int) WhiteKing;
9274 /* Code added by Tord: */
9275 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9276 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9277 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9278 board[fromY][fromX] = EmptySquare;
9279 board[toY][toX] = EmptySquare;
9280 if((toX > fromX) != (piece == WhiteRook)) {
9281 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9283 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9285 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9286 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9287 board[fromY][fromX] = EmptySquare;
9288 board[toY][toX] = EmptySquare;
9289 if((toX > fromX) != (piece == BlackRook)) {
9290 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9292 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9294 /* End of code added by Tord */
9296 } else if (board[fromY][fromX] == king
9297 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9298 && toY == fromY && toX > fromX+1) {
9299 board[fromY][fromX] = EmptySquare;
9300 board[toY][toX] = king;
9301 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9302 board[fromY][BOARD_RGHT-1] = EmptySquare;
9303 } else if (board[fromY][fromX] == king
9304 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9305 && toY == fromY && toX < fromX-1) {
9306 board[fromY][fromX] = EmptySquare;
9307 board[toY][toX] = king;
9308 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9309 board[fromY][BOARD_LEFT] = EmptySquare;
9310 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9311 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9312 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9314 /* white pawn promotion */
9315 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9316 if(gameInfo.variant==VariantBughouse ||
9317 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9318 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9319 board[fromY][fromX] = EmptySquare;
9320 } else if ((fromY >= BOARD_HEIGHT>>1)
9322 && gameInfo.variant != VariantXiangqi
9323 && gameInfo.variant != VariantBerolina
9324 && (board[fromY][fromX] == WhitePawn)
9325 && (board[toY][toX] == EmptySquare)) {
9326 board[fromY][fromX] = EmptySquare;
9327 board[toY][toX] = WhitePawn;
9328 captured = board[toY - 1][toX];
9329 board[toY - 1][toX] = EmptySquare;
9330 } else if ((fromY == BOARD_HEIGHT-4)
9332 && gameInfo.variant == VariantBerolina
9333 && (board[fromY][fromX] == WhitePawn)
9334 && (board[toY][toX] == EmptySquare)) {
9335 board[fromY][fromX] = EmptySquare;
9336 board[toY][toX] = WhitePawn;
9337 if(oldEP & EP_BEROLIN_A) {
9338 captured = board[fromY][fromX-1];
9339 board[fromY][fromX-1] = EmptySquare;
9340 }else{ captured = board[fromY][fromX+1];
9341 board[fromY][fromX+1] = EmptySquare;
9343 } else if (board[fromY][fromX] == king
9344 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9345 && toY == fromY && toX > fromX+1) {
9346 board[fromY][fromX] = EmptySquare;
9347 board[toY][toX] = king;
9348 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9349 board[fromY][BOARD_RGHT-1] = EmptySquare;
9350 } else if (board[fromY][fromX] == king
9351 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9352 && toY == fromY && toX < fromX-1) {
9353 board[fromY][fromX] = EmptySquare;
9354 board[toY][toX] = king;
9355 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9356 board[fromY][BOARD_LEFT] = EmptySquare;
9357 } else if (fromY == 7 && fromX == 3
9358 && board[fromY][fromX] == BlackKing
9359 && toY == 7 && toX == 5) {
9360 board[fromY][fromX] = EmptySquare;
9361 board[toY][toX] = BlackKing;
9362 board[fromY][7] = EmptySquare;
9363 board[toY][4] = BlackRook;
9364 } else if (fromY == 7 && fromX == 3
9365 && board[fromY][fromX] == BlackKing
9366 && toY == 7 && toX == 1) {
9367 board[fromY][fromX] = EmptySquare;
9368 board[toY][toX] = BlackKing;
9369 board[fromY][0] = EmptySquare;
9370 board[toY][2] = BlackRook;
9371 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9372 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9373 && toY < promoRank && promoChar
9375 /* black pawn promotion */
9376 board[toY][toX] = CharToPiece(ToLower(promoChar));
9377 if(gameInfo.variant==VariantBughouse ||
9378 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9379 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9380 board[fromY][fromX] = EmptySquare;
9381 } else if ((fromY < BOARD_HEIGHT>>1)
9383 && gameInfo.variant != VariantXiangqi
9384 && gameInfo.variant != VariantBerolina
9385 && (board[fromY][fromX] == BlackPawn)
9386 && (board[toY][toX] == EmptySquare)) {
9387 board[fromY][fromX] = EmptySquare;
9388 board[toY][toX] = BlackPawn;
9389 captured = board[toY + 1][toX];
9390 board[toY + 1][toX] = EmptySquare;
9391 } else if ((fromY == 3)
9393 && gameInfo.variant == VariantBerolina
9394 && (board[fromY][fromX] == BlackPawn)
9395 && (board[toY][toX] == EmptySquare)) {
9396 board[fromY][fromX] = EmptySquare;
9397 board[toY][toX] = BlackPawn;
9398 if(oldEP & EP_BEROLIN_A) {
9399 captured = board[fromY][fromX-1];
9400 board[fromY][fromX-1] = EmptySquare;
9401 }else{ captured = board[fromY][fromX+1];
9402 board[fromY][fromX+1] = EmptySquare;
9405 board[toY][toX] = board[fromY][fromX];
9406 board[fromY][fromX] = EmptySquare;
9410 if (gameInfo.holdingsWidth != 0) {
9412 /* !!A lot more code needs to be written to support holdings */
9413 /* [HGM] OK, so I have written it. Holdings are stored in the */
9414 /* penultimate board files, so they are automaticlly stored */
9415 /* in the game history. */
9416 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9417 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9418 /* Delete from holdings, by decreasing count */
9419 /* and erasing image if necessary */
9420 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9421 if(p < (int) BlackPawn) { /* white drop */
9422 p -= (int)WhitePawn;
9423 p = PieceToNumber((ChessSquare)p);
9424 if(p >= gameInfo.holdingsSize) p = 0;
9425 if(--board[p][BOARD_WIDTH-2] <= 0)
9426 board[p][BOARD_WIDTH-1] = EmptySquare;
9427 if((int)board[p][BOARD_WIDTH-2] < 0)
9428 board[p][BOARD_WIDTH-2] = 0;
9429 } else { /* black drop */
9430 p -= (int)BlackPawn;
9431 p = PieceToNumber((ChessSquare)p);
9432 if(p >= gameInfo.holdingsSize) p = 0;
9433 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9434 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9435 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9436 board[BOARD_HEIGHT-1-p][1] = 0;
9439 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9440 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9441 /* [HGM] holdings: Add to holdings, if holdings exist */
9442 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9443 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9444 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9447 if (p >= (int) BlackPawn) {
9448 p -= (int)BlackPawn;
9449 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9450 /* in Shogi restore piece to its original first */
9451 captured = (ChessSquare) (DEMOTED captured);
9454 p = PieceToNumber((ChessSquare)p);
9455 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9456 board[p][BOARD_WIDTH-2]++;
9457 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9459 p -= (int)WhitePawn;
9460 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9461 captured = (ChessSquare) (DEMOTED captured);
9464 p = PieceToNumber((ChessSquare)p);
9465 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9466 board[BOARD_HEIGHT-1-p][1]++;
9467 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9470 } else if (gameInfo.variant == VariantAtomic) {
9471 if (captured != EmptySquare) {
9473 for (y = toY-1; y <= toY+1; y++) {
9474 for (x = toX-1; x <= toX+1; x++) {
9475 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9476 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9477 board[y][x] = EmptySquare;
9481 board[toY][toX] = EmptySquare;
9484 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9485 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9487 if(promoChar == '+') {
9488 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9489 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9490 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9491 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9492 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9493 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9494 board[toY][toX] = newPiece;
9496 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9497 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9498 // [HGM] superchess: take promotion piece out of holdings
9499 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9500 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9501 if(!--board[k][BOARD_WIDTH-2])
9502 board[k][BOARD_WIDTH-1] = EmptySquare;
9504 if(!--board[BOARD_HEIGHT-1-k][1])
9505 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9511 /* Updates forwardMostMove */
9513 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9515 // forwardMostMove++; // [HGM] bare: moved downstream
9517 (void) CoordsToAlgebraic(boards[forwardMostMove],
9518 PosFlags(forwardMostMove),
9519 fromY, fromX, toY, toX, promoChar,
9520 parseList[forwardMostMove]);
9522 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9523 int timeLeft; static int lastLoadFlag=0; int king, piece;
9524 piece = boards[forwardMostMove][fromY][fromX];
9525 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9526 if(gameInfo.variant == VariantKnightmate)
9527 king += (int) WhiteUnicorn - (int) WhiteKing;
9528 if(forwardMostMove == 0) {
9529 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9530 fprintf(serverMoves, "%s;", UserName());
9531 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9532 fprintf(serverMoves, "%s;", second.tidy);
9533 fprintf(serverMoves, "%s;", first.tidy);
9534 if(gameMode == MachinePlaysWhite)
9535 fprintf(serverMoves, "%s;", UserName());
9536 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9537 fprintf(serverMoves, "%s;", second.tidy);
9538 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9539 lastLoadFlag = loadFlag;
9541 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9542 // print castling suffix
9543 if( toY == fromY && piece == king ) {
9545 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9547 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9550 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9551 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9552 boards[forwardMostMove][toY][toX] == EmptySquare
9553 && fromX != toX && fromY != toY)
9554 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9556 if(promoChar != NULLCHAR)
9557 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9559 char buf[MOVE_LEN*2], *p; int len;
9560 fprintf(serverMoves, "/%d/%d",
9561 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9562 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9563 else timeLeft = blackTimeRemaining/1000;
9564 fprintf(serverMoves, "/%d", timeLeft);
9565 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9566 if(p = strchr(buf, '=')) *p = NULLCHAR;
9567 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9568 fprintf(serverMoves, "/%s", buf);
9570 fflush(serverMoves);
9573 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9574 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9577 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9578 if (commentList[forwardMostMove+1] != NULL) {
9579 free(commentList[forwardMostMove+1]);
9580 commentList[forwardMostMove+1] = NULL;
9582 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9583 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9584 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9585 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9586 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9587 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9588 adjustedClock = FALSE;
9589 gameInfo.result = GameUnfinished;
9590 if (gameInfo.resultDetails != NULL) {
9591 free(gameInfo.resultDetails);
9592 gameInfo.resultDetails = NULL;
9594 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9595 moveList[forwardMostMove - 1]);
9596 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9602 if(gameInfo.variant != VariantShogi)
9603 strcat(parseList[forwardMostMove - 1], "+");
9607 strcat(parseList[forwardMostMove - 1], "#");
9613 /* Updates currentMove if not pausing */
9615 ShowMove (int fromX, int fromY, int toX, int toY)
9617 int instant = (gameMode == PlayFromGameFile) ?
9618 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9619 if(appData.noGUI) return;
9620 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9622 if (forwardMostMove == currentMove + 1) {
9623 AnimateMove(boards[forwardMostMove - 1],
9624 fromX, fromY, toX, toY);
9626 if (appData.highlightLastMove) {
9627 SetHighlights(fromX, fromY, toX, toY);
9630 currentMove = forwardMostMove;
9633 if (instant) return;
9635 DisplayMove(currentMove - 1);
9636 DrawPosition(FALSE, boards[currentMove]);
9637 DisplayBothClocks();
9638 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9642 SendEgtPath (ChessProgramState *cps)
9643 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9644 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9646 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9649 char c, *q = name+1, *r, *s;
9651 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9652 while(*p && *p != ',') *q++ = *p++;
9654 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9655 strcmp(name, ",nalimov:") == 0 ) {
9656 // take nalimov path from the menu-changeable option first, if it is defined
9657 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9658 SendToProgram(buf,cps); // send egtbpath command for nalimov
9660 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9661 (s = StrStr(appData.egtFormats, name)) != NULL) {
9662 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9663 s = r = StrStr(s, ":") + 1; // beginning of path info
9664 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9665 c = *r; *r = 0; // temporarily null-terminate path info
9666 *--q = 0; // strip of trailig ':' from name
9667 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9669 SendToProgram(buf,cps); // send egtbpath command for this format
9671 if(*p == ',') p++; // read away comma to position for next format name
9676 InitChessProgram (ChessProgramState *cps, int setup)
9677 /* setup needed to setup FRC opening position */
9679 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9680 if (appData.noChessProgram) return;
9681 hintRequested = FALSE;
9682 bookRequested = FALSE;
9684 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9685 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9686 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9687 if(cps->memSize) { /* [HGM] memory */
9688 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9689 SendToProgram(buf, cps);
9691 SendEgtPath(cps); /* [HGM] EGT */
9692 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9693 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9694 SendToProgram(buf, cps);
9697 SendToProgram(cps->initString, cps);
9698 if (gameInfo.variant != VariantNormal &&
9699 gameInfo.variant != VariantLoadable
9700 /* [HGM] also send variant if board size non-standard */
9701 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9703 char *v = VariantName(gameInfo.variant);
9704 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9705 /* [HGM] in protocol 1 we have to assume all variants valid */
9706 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9707 DisplayFatalError(buf, 0, 1);
9711 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9712 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9713 if( gameInfo.variant == VariantXiangqi )
9714 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9715 if( gameInfo.variant == VariantShogi )
9716 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9717 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9718 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9719 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9720 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9721 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9722 if( gameInfo.variant == VariantCourier )
9723 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9724 if( gameInfo.variant == VariantSuper )
9725 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9726 if( gameInfo.variant == VariantGreat )
9727 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9728 if( gameInfo.variant == VariantSChess )
9729 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9730 if( gameInfo.variant == VariantGrand )
9731 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9734 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9735 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9736 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9737 if(StrStr(cps->variants, b) == NULL) {
9738 // specific sized variant not known, check if general sizing allowed
9739 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9740 if(StrStr(cps->variants, "boardsize") == NULL) {
9741 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9742 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9743 DisplayFatalError(buf, 0, 1);
9746 /* [HGM] here we really should compare with the maximum supported board size */
9749 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9750 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9751 SendToProgram(buf, cps);
9753 currentlyInitializedVariant = gameInfo.variant;
9755 /* [HGM] send opening position in FRC to first engine */
9757 SendToProgram("force\n", cps);
9759 /* engine is now in force mode! Set flag to wake it up after first move. */
9760 setboardSpoiledMachineBlack = 1;
9764 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9765 SendToProgram(buf, cps);
9767 cps->maybeThinking = FALSE;
9768 cps->offeredDraw = 0;
9769 if (!appData.icsActive) {
9770 SendTimeControl(cps, movesPerSession, timeControl,
9771 timeIncrement, appData.searchDepth,
9774 if (appData.showThinking
9775 // [HGM] thinking: four options require thinking output to be sent
9776 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9778 SendToProgram("post\n", cps);
9780 SendToProgram("hard\n", cps);
9781 if (!appData.ponderNextMove) {
9782 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9783 it without being sure what state we are in first. "hard"
9784 is not a toggle, so that one is OK.
9786 SendToProgram("easy\n", cps);
9789 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9790 SendToProgram(buf, cps);
9792 cps->initDone = TRUE;
9793 ClearEngineOutputPane(cps == &second);
9798 StartChessProgram (ChessProgramState *cps)
9803 if (appData.noChessProgram) return;
9804 cps->initDone = FALSE;
9806 if (strcmp(cps->host, "localhost") == 0) {
9807 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9808 } else if (*appData.remoteShell == NULLCHAR) {
9809 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9811 if (*appData.remoteUser == NULLCHAR) {
9812 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9815 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9816 cps->host, appData.remoteUser, cps->program);
9818 err = StartChildProcess(buf, "", &cps->pr);
9822 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9823 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9824 if(cps != &first) return;
9825 appData.noChessProgram = TRUE;
9828 // DisplayFatalError(buf, err, 1);
9829 // cps->pr = NoProc;
9834 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9835 if (cps->protocolVersion > 1) {
9836 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9837 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9838 cps->comboCnt = 0; // and values of combo boxes
9839 SendToProgram(buf, cps);
9841 SendToProgram("xboard\n", cps);
9846 TwoMachinesEventIfReady P((void))
9848 static int curMess = 0;
9849 if (first.lastPing != first.lastPong) {
9850 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9851 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9854 if (second.lastPing != second.lastPong) {
9855 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9856 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9859 DisplayMessage("", ""); curMess = 0;
9865 MakeName (char *template)
9869 static char buf[MSG_SIZ];
9873 clock = time((time_t *)NULL);
9874 tm = localtime(&clock);
9876 while(*p++ = *template++) if(p[-1] == '%') {
9877 switch(*template++) {
9878 case 0: *p = 0; return buf;
9879 case 'Y': i = tm->tm_year+1900; break;
9880 case 'y': i = tm->tm_year-100; break;
9881 case 'M': i = tm->tm_mon+1; break;
9882 case 'd': i = tm->tm_mday; break;
9883 case 'h': i = tm->tm_hour; break;
9884 case 'm': i = tm->tm_min; break;
9885 case 's': i = tm->tm_sec; break;
9888 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9894 CountPlayers (char *p)
9897 while(p = strchr(p, '\n')) p++, n++; // count participants
9902 WriteTourneyFile (char *results, FILE *f)
9903 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9904 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9905 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9906 // create a file with tournament description
9907 fprintf(f, "-participants {%s}\n", appData.participants);
9908 fprintf(f, "-seedBase %d\n", appData.seedBase);
9909 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9910 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9911 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9912 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9913 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9914 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9915 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9916 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9917 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9918 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9919 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9920 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9922 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9924 fprintf(f, "-mps %d\n", appData.movesPerSession);
9925 fprintf(f, "-tc %s\n", appData.timeControl);
9926 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9928 fprintf(f, "-results \"%s\"\n", results);
9933 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9936 Substitute (char *participants, int expunge)
9938 int i, changed, changes=0, nPlayers=0;
9939 char *p, *q, *r, buf[MSG_SIZ];
9940 if(participants == NULL) return;
9941 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9942 r = p = participants; q = appData.participants;
9943 while(*p && *p == *q) {
9944 if(*p == '\n') r = p+1, nPlayers++;
9947 if(*p) { // difference
9948 while(*p && *p++ != '\n');
9949 while(*q && *q++ != '\n');
9951 changes = 1 + (strcmp(p, q) != 0);
9953 if(changes == 1) { // a single engine mnemonic was changed
9954 q = r; while(*q) nPlayers += (*q++ == '\n');
9955 p = buf; while(*r && (*p = *r++) != '\n') p++;
9957 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9958 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9959 if(mnemonic[i]) { // The substitute is valid
9961 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9962 flock(fileno(f), LOCK_EX);
9963 ParseArgsFromFile(f);
9964 fseek(f, 0, SEEK_SET);
9965 FREE(appData.participants); appData.participants = participants;
9966 if(expunge) { // erase results of replaced engine
9967 int len = strlen(appData.results), w, b, dummy;
9968 for(i=0; i<len; i++) {
9969 Pairing(i, nPlayers, &w, &b, &dummy);
9970 if((w == changed || b == changed) && appData.results[i] == '*') {
9971 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9976 for(i=0; i<len; i++) {
9977 Pairing(i, nPlayers, &w, &b, &dummy);
9978 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9981 WriteTourneyFile(appData.results, f);
9982 fclose(f); // release lock
9985 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9987 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9988 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9994 CreateTourney (char *name)
9997 if(matchMode && strcmp(name, appData.tourneyFile)) {
9998 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10000 if(name[0] == NULLCHAR) {
10001 if(appData.participants[0])
10002 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10005 f = fopen(name, "r");
10006 if(f) { // file exists
10007 ASSIGN(appData.tourneyFile, name);
10008 ParseArgsFromFile(f); // parse it
10010 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10011 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10012 DisplayError(_("Not enough participants"), 0);
10015 ASSIGN(appData.tourneyFile, name);
10016 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10017 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10020 appData.noChessProgram = FALSE;
10021 appData.clockMode = TRUE;
10027 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10029 char buf[MSG_SIZ], *p, *q;
10030 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10031 skip = !all && group[0]; // if group requested, we start in skip mode
10032 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10033 p = names; q = buf; header = 0;
10034 while(*p && *p != '\n') *q++ = *p++;
10036 if(*p == '\n') p++;
10037 if(buf[0] == '#') {
10038 if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10039 depth++; // we must be entering a new group
10040 if(all) continue; // suppress printing group headers when complete list requested
10042 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10044 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10045 if(engineList[i]) free(engineList[i]);
10046 engineList[i] = strdup(buf);
10047 if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10048 if(engineMnemonic[i]) free(engineMnemonic[i]);
10049 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10051 sscanf(q + 8, "%s", buf + strlen(buf));
10054 engineMnemonic[i] = strdup(buf);
10057 engineList[i] = engineMnemonic[i] = NULL;
10061 // following implemented as macro to avoid type limitations
10062 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10065 SwapEngines (int n)
10066 { // swap settings for first engine and other engine (so far only some selected options)
10071 SWAP(chessProgram, p)
10073 SWAP(hasOwnBookUCI, h)
10074 SWAP(protocolVersion, h)
10076 SWAP(scoreIsAbsolute, h)
10081 SWAP(engOptions, p)
10082 SWAP(engInitString, p)
10083 SWAP(computerString, p)
10085 SWAP(fenOverride, p)
10087 SWAP(accumulateTC, h)
10092 SetPlayer (int player, char *p)
10093 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10095 char buf[MSG_SIZ], *engineName;
10096 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10097 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10098 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10100 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10101 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10102 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10103 ParseArgsFromString(buf);
10109 char *recentEngines;
10112 RecentEngineEvent (int nr)
10115 // SwapEngines(1); // bump first to second
10116 // ReplaceEngine(&second, 1); // and load it there
10117 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10118 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10119 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10120 ReplaceEngine(&first, 0);
10121 FloatToFront(&appData.recentEngineList, command[n]);
10126 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10127 { // determine players from game number
10128 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10130 if(appData.tourneyType == 0) {
10131 roundsPerCycle = (nPlayers - 1) | 1;
10132 pairingsPerRound = nPlayers / 2;
10133 } else if(appData.tourneyType > 0) {
10134 roundsPerCycle = nPlayers - appData.tourneyType;
10135 pairingsPerRound = appData.tourneyType;
10137 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10138 gamesPerCycle = gamesPerRound * roundsPerCycle;
10139 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10140 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10141 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10142 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10143 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10144 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10146 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10147 if(appData.roundSync) *syncInterval = gamesPerRound;
10149 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10151 if(appData.tourneyType == 0) {
10152 if(curPairing == (nPlayers-1)/2 ) {
10153 *whitePlayer = curRound;
10154 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10156 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10157 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10158 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10159 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10161 } else if(appData.tourneyType > 1) {
10162 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10163 *whitePlayer = curRound + appData.tourneyType;
10164 } else if(appData.tourneyType > 0) {
10165 *whitePlayer = curPairing;
10166 *blackPlayer = curRound + appData.tourneyType;
10169 // take care of white/black alternation per round.
10170 // For cycles and games this is already taken care of by default, derived from matchGame!
10171 return curRound & 1;
10175 NextTourneyGame (int nr, int *swapColors)
10176 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10178 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10180 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10181 tf = fopen(appData.tourneyFile, "r");
10182 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10183 ParseArgsFromFile(tf); fclose(tf);
10184 InitTimeControls(); // TC might be altered from tourney file
10186 nPlayers = CountPlayers(appData.participants); // count participants
10187 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10188 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10191 p = q = appData.results;
10192 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10193 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10194 DisplayMessage(_("Waiting for other game(s)"),"");
10195 waitingForGame = TRUE;
10196 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10199 waitingForGame = FALSE;
10202 if(appData.tourneyType < 0) {
10203 if(nr>=0 && !pairingReceived) {
10205 if(pairing.pr == NoProc) {
10206 if(!appData.pairingEngine[0]) {
10207 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10210 StartChessProgram(&pairing); // starts the pairing engine
10212 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10213 SendToProgram(buf, &pairing);
10214 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10215 SendToProgram(buf, &pairing);
10216 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10218 pairingReceived = 0; // ... so we continue here
10220 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10221 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10222 matchGame = 1; roundNr = nr / syncInterval + 1;
10225 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10227 // redefine engines, engine dir, etc.
10228 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10229 if(first.pr == NoProc) {
10230 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10231 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10233 if(second.pr == NoProc) {
10235 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10236 SwapEngines(1); // and make that valid for second engine by swapping
10237 InitEngine(&second, 1);
10239 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10240 UpdateLogos(FALSE); // leave display to ModeHiglight()
10246 { // performs game initialization that does not invoke engines, and then tries to start the game
10247 int res, firstWhite, swapColors = 0;
10248 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10249 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
10251 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10252 if(strcmp(buf, currentDebugFile)) { // name has changed
10253 FILE *f = fopen(buf, "w");
10254 if(f) { // if opening the new file failed, just keep using the old one
10255 ASSIGN(currentDebugFile, buf);
10259 if(appData.serverFileName) {
10260 if(serverFP) fclose(serverFP);
10261 serverFP = fopen(appData.serverFileName, "w");
10262 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10263 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10267 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10268 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10269 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10270 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10271 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10272 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10273 Reset(FALSE, first.pr != NoProc);
10274 res = LoadGameOrPosition(matchGame); // setup game
10275 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10276 if(!res) return; // abort when bad game/pos file
10277 TwoMachinesEvent();
10281 UserAdjudicationEvent (int result)
10283 ChessMove gameResult = GameIsDrawn;
10286 gameResult = WhiteWins;
10288 else if( result < 0 ) {
10289 gameResult = BlackWins;
10292 if( gameMode == TwoMachinesPlay ) {
10293 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10298 // [HGM] save: calculate checksum of game to make games easily identifiable
10300 StringCheckSum (char *s)
10303 if(s==NULL) return 0;
10304 while(*s) i = i*259 + *s++;
10312 for(i=backwardMostMove; i<forwardMostMove; i++) {
10313 sum += pvInfoList[i].depth;
10314 sum += StringCheckSum(parseList[i]);
10315 sum += StringCheckSum(commentList[i]);
10318 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10319 return sum + StringCheckSum(commentList[i]);
10320 } // end of save patch
10323 GameEnds (ChessMove result, char *resultDetails, int whosays)
10325 GameMode nextGameMode;
10327 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10329 if(endingGame) return; /* [HGM] crash: forbid recursion */
10331 if(twoBoards) { // [HGM] dual: switch back to one board
10332 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10333 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10335 if (appData.debugMode) {
10336 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10337 result, resultDetails ? resultDetails : "(null)", whosays);
10340 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10342 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10343 /* If we are playing on ICS, the server decides when the
10344 game is over, but the engine can offer to draw, claim
10348 if (appData.zippyPlay && first.initDone) {
10349 if (result == GameIsDrawn) {
10350 /* In case draw still needs to be claimed */
10351 SendToICS(ics_prefix);
10352 SendToICS("draw\n");
10353 } else if (StrCaseStr(resultDetails, "resign")) {
10354 SendToICS(ics_prefix);
10355 SendToICS("resign\n");
10359 endingGame = 0; /* [HGM] crash */
10363 /* If we're loading the game from a file, stop */
10364 if (whosays == GE_FILE) {
10365 (void) StopLoadGameTimer();
10369 /* Cancel draw offers */
10370 first.offeredDraw = second.offeredDraw = 0;
10372 /* If this is an ICS game, only ICS can really say it's done;
10373 if not, anyone can. */
10374 isIcsGame = (gameMode == IcsPlayingWhite ||
10375 gameMode == IcsPlayingBlack ||
10376 gameMode == IcsObserving ||
10377 gameMode == IcsExamining);
10379 if (!isIcsGame || whosays == GE_ICS) {
10380 /* OK -- not an ICS game, or ICS said it was done */
10382 if (!isIcsGame && !appData.noChessProgram)
10383 SetUserThinkingEnables();
10385 /* [HGM] if a machine claims the game end we verify this claim */
10386 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10387 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10389 ChessMove trueResult = (ChessMove) -1;
10391 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10392 first.twoMachinesColor[0] :
10393 second.twoMachinesColor[0] ;
10395 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10396 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10397 /* [HGM] verify: engine mate claims accepted if they were flagged */
10398 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10400 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10401 /* [HGM] verify: engine mate claims accepted if they were flagged */
10402 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10404 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10405 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10408 // now verify win claims, but not in drop games, as we don't understand those yet
10409 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10410 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10411 (result == WhiteWins && claimer == 'w' ||
10412 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10413 if (appData.debugMode) {
10414 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10415 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10417 if(result != trueResult) {
10418 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10419 result = claimer == 'w' ? BlackWins : WhiteWins;
10420 resultDetails = buf;
10423 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10424 && (forwardMostMove <= backwardMostMove ||
10425 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10426 (claimer=='b')==(forwardMostMove&1))
10428 /* [HGM] verify: draws that were not flagged are false claims */
10429 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10430 result = claimer == 'w' ? BlackWins : WhiteWins;
10431 resultDetails = buf;
10433 /* (Claiming a loss is accepted no questions asked!) */
10435 /* [HGM] bare: don't allow bare King to win */
10436 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10437 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10438 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10439 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10440 && result != GameIsDrawn)
10441 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10442 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10443 int p = (signed char)boards[forwardMostMove][i][j] - color;
10444 if(p >= 0 && p <= (int)WhiteKing) k++;
10446 if (appData.debugMode) {
10447 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10448 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10451 result = GameIsDrawn;
10452 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10453 resultDetails = buf;
10459 if(serverMoves != NULL && !loadFlag) { char c = '=';
10460 if(result==WhiteWins) c = '+';
10461 if(result==BlackWins) c = '-';
10462 if(resultDetails != NULL)
10463 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10465 if (resultDetails != NULL) {
10466 gameInfo.result = result;
10467 gameInfo.resultDetails = StrSave(resultDetails);
10469 /* display last move only if game was not loaded from file */
10470 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10471 DisplayMove(currentMove - 1);
10473 if (forwardMostMove != 0) {
10474 if (gameMode != PlayFromGameFile && gameMode != EditGame
10475 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10477 if (*appData.saveGameFile != NULLCHAR) {
10478 SaveGameToFile(appData.saveGameFile, TRUE);
10479 } else if (appData.autoSaveGames) {
10482 if (*appData.savePositionFile != NULLCHAR) {
10483 SavePositionToFile(appData.savePositionFile);
10488 /* Tell program how game ended in case it is learning */
10489 /* [HGM] Moved this to after saving the PGN, just in case */
10490 /* engine died and we got here through time loss. In that */
10491 /* case we will get a fatal error writing the pipe, which */
10492 /* would otherwise lose us the PGN. */
10493 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10494 /* output during GameEnds should never be fatal anymore */
10495 if (gameMode == MachinePlaysWhite ||
10496 gameMode == MachinePlaysBlack ||
10497 gameMode == TwoMachinesPlay ||
10498 gameMode == IcsPlayingWhite ||
10499 gameMode == IcsPlayingBlack ||
10500 gameMode == BeginningOfGame) {
10502 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10504 if (first.pr != NoProc) {
10505 SendToProgram(buf, &first);
10507 if (second.pr != NoProc &&
10508 gameMode == TwoMachinesPlay) {
10509 SendToProgram(buf, &second);
10514 if (appData.icsActive) {
10515 if (appData.quietPlay &&
10516 (gameMode == IcsPlayingWhite ||
10517 gameMode == IcsPlayingBlack)) {
10518 SendToICS(ics_prefix);
10519 SendToICS("set shout 1\n");
10521 nextGameMode = IcsIdle;
10522 ics_user_moved = FALSE;
10523 /* clean up premove. It's ugly when the game has ended and the
10524 * premove highlights are still on the board.
10527 gotPremove = FALSE;
10528 ClearPremoveHighlights();
10529 DrawPosition(FALSE, boards[currentMove]);
10531 if (whosays == GE_ICS) {
10534 if (gameMode == IcsPlayingWhite)
10536 else if(gameMode == IcsPlayingBlack)
10537 PlayIcsLossSound();
10540 if (gameMode == IcsPlayingBlack)
10542 else if(gameMode == IcsPlayingWhite)
10543 PlayIcsLossSound();
10546 PlayIcsDrawSound();
10549 PlayIcsUnfinishedSound();
10552 } else if (gameMode == EditGame ||
10553 gameMode == PlayFromGameFile ||
10554 gameMode == AnalyzeMode ||
10555 gameMode == AnalyzeFile) {
10556 nextGameMode = gameMode;
10558 nextGameMode = EndOfGame;
10563 nextGameMode = gameMode;
10566 if (appData.noChessProgram) {
10567 gameMode = nextGameMode;
10569 endingGame = 0; /* [HGM] crash */
10574 /* Put first chess program into idle state */
10575 if (first.pr != NoProc &&
10576 (gameMode == MachinePlaysWhite ||
10577 gameMode == MachinePlaysBlack ||
10578 gameMode == TwoMachinesPlay ||
10579 gameMode == IcsPlayingWhite ||
10580 gameMode == IcsPlayingBlack ||
10581 gameMode == BeginningOfGame)) {
10582 SendToProgram("force\n", &first);
10583 if (first.usePing) {
10585 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10586 SendToProgram(buf, &first);
10589 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10590 /* Kill off first chess program */
10591 if (first.isr != NULL)
10592 RemoveInputSource(first.isr);
10595 if (first.pr != NoProc) {
10597 DoSleep( appData.delayBeforeQuit );
10598 SendToProgram("quit\n", &first);
10599 DoSleep( appData.delayAfterQuit );
10600 DestroyChildProcess(first.pr, first.useSigterm);
10604 if (second.reuse) {
10605 /* Put second chess program into idle state */
10606 if (second.pr != NoProc &&
10607 gameMode == TwoMachinesPlay) {
10608 SendToProgram("force\n", &second);
10609 if (second.usePing) {
10611 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10612 SendToProgram(buf, &second);
10615 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10616 /* Kill off second chess program */
10617 if (second.isr != NULL)
10618 RemoveInputSource(second.isr);
10621 if (second.pr != NoProc) {
10622 DoSleep( appData.delayBeforeQuit );
10623 SendToProgram("quit\n", &second);
10624 DoSleep( appData.delayAfterQuit );
10625 DestroyChildProcess(second.pr, second.useSigterm);
10627 second.pr = NoProc;
10630 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10631 char resChar = '=';
10635 if (first.twoMachinesColor[0] == 'w') {
10638 second.matchWins++;
10643 if (first.twoMachinesColor[0] == 'b') {
10646 second.matchWins++;
10649 case GameUnfinished:
10655 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10656 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10657 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10658 ReserveGame(nextGame, resChar); // sets nextGame
10659 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10660 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10661 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10663 if (nextGame <= appData.matchGames && !abortMatch) {
10664 gameMode = nextGameMode;
10665 matchGame = nextGame; // this will be overruled in tourney mode!
10666 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10667 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10668 endingGame = 0; /* [HGM] crash */
10671 gameMode = nextGameMode;
10672 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10673 first.tidy, second.tidy,
10674 first.matchWins, second.matchWins,
10675 appData.matchGames - (first.matchWins + second.matchWins));
10676 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10677 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10678 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10679 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10680 first.twoMachinesColor = "black\n";
10681 second.twoMachinesColor = "white\n";
10683 first.twoMachinesColor = "white\n";
10684 second.twoMachinesColor = "black\n";
10688 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10689 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10691 gameMode = nextGameMode;
10693 endingGame = 0; /* [HGM] crash */
10694 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10695 if(matchMode == TRUE) { // match through command line: exit with or without popup
10697 ToNrEvent(forwardMostMove);
10698 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10700 } else DisplayFatalError(buf, 0, 0);
10701 } else { // match through menu; just stop, with or without popup
10702 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10705 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10706 } else DisplayNote(buf);
10708 if(ranking) free(ranking);
10712 /* Assumes program was just initialized (initString sent).
10713 Leaves program in force mode. */
10715 FeedMovesToProgram (ChessProgramState *cps, int upto)
10719 if (appData.debugMode)
10720 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10721 startedFromSetupPosition ? "position and " : "",
10722 backwardMostMove, upto, cps->which);
10723 if(currentlyInitializedVariant != gameInfo.variant) {
10725 // [HGM] variantswitch: make engine aware of new variant
10726 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10727 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10728 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10729 SendToProgram(buf, cps);
10730 currentlyInitializedVariant = gameInfo.variant;
10732 SendToProgram("force\n", cps);
10733 if (startedFromSetupPosition) {
10734 SendBoard(cps, backwardMostMove);
10735 if (appData.debugMode) {
10736 fprintf(debugFP, "feedMoves\n");
10739 for (i = backwardMostMove; i < upto; i++) {
10740 SendMoveToProgram(i, cps);
10746 ResurrectChessProgram ()
10748 /* The chess program may have exited.
10749 If so, restart it and feed it all the moves made so far. */
10750 static int doInit = 0;
10752 if (appData.noChessProgram) return 1;
10754 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10755 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10756 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10757 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10759 if (first.pr != NoProc) return 1;
10760 StartChessProgram(&first);
10762 InitChessProgram(&first, FALSE);
10763 FeedMovesToProgram(&first, currentMove);
10765 if (!first.sendTime) {
10766 /* can't tell gnuchess what its clock should read,
10767 so we bow to its notion. */
10769 timeRemaining[0][currentMove] = whiteTimeRemaining;
10770 timeRemaining[1][currentMove] = blackTimeRemaining;
10773 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10774 appData.icsEngineAnalyze) && first.analysisSupport) {
10775 SendToProgram("analyze\n", &first);
10776 first.analyzing = TRUE;
10782 * Button procedures
10785 Reset (int redraw, int init)
10789 if (appData.debugMode) {
10790 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10791 redraw, init, gameMode);
10793 CleanupTail(); // [HGM] vari: delete any stored variations
10794 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10795 pausing = pauseExamInvalid = FALSE;
10796 startedFromSetupPosition = blackPlaysFirst = FALSE;
10798 whiteFlag = blackFlag = FALSE;
10799 userOfferedDraw = FALSE;
10800 hintRequested = bookRequested = FALSE;
10801 first.maybeThinking = FALSE;
10802 second.maybeThinking = FALSE;
10803 first.bookSuspend = FALSE; // [HGM] book
10804 second.bookSuspend = FALSE;
10805 thinkOutput[0] = NULLCHAR;
10806 lastHint[0] = NULLCHAR;
10807 ClearGameInfo(&gameInfo);
10808 gameInfo.variant = StringToVariant(appData.variant);
10809 ics_user_moved = ics_clock_paused = FALSE;
10810 ics_getting_history = H_FALSE;
10812 white_holding[0] = black_holding[0] = NULLCHAR;
10813 ClearProgramStats();
10814 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10818 flipView = appData.flipView;
10819 ClearPremoveHighlights();
10820 gotPremove = FALSE;
10821 alarmSounded = FALSE;
10823 GameEnds(EndOfFile, NULL, GE_PLAYER);
10824 if(appData.serverMovesName != NULL) {
10825 /* [HGM] prepare to make moves file for broadcasting */
10826 clock_t t = clock();
10827 if(serverMoves != NULL) fclose(serverMoves);
10828 serverMoves = fopen(appData.serverMovesName, "r");
10829 if(serverMoves != NULL) {
10830 fclose(serverMoves);
10831 /* delay 15 sec before overwriting, so all clients can see end */
10832 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10834 serverMoves = fopen(appData.serverMovesName, "w");
10838 gameMode = BeginningOfGame;
10840 if(appData.icsActive) gameInfo.variant = VariantNormal;
10841 currentMove = forwardMostMove = backwardMostMove = 0;
10842 MarkTargetSquares(1);
10843 InitPosition(redraw);
10844 for (i = 0; i < MAX_MOVES; i++) {
10845 if (commentList[i] != NULL) {
10846 free(commentList[i]);
10847 commentList[i] = NULL;
10851 timeRemaining[0][0] = whiteTimeRemaining;
10852 timeRemaining[1][0] = blackTimeRemaining;
10854 if (first.pr == NoProc) {
10855 StartChessProgram(&first);
10858 InitChessProgram(&first, startedFromSetupPosition);
10861 DisplayMessage("", "");
10862 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10863 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10864 ClearMap(); // [HGM] exclude: invalidate map
10868 AutoPlayGameLoop ()
10871 if (!AutoPlayOneMove())
10873 if (matchMode || appData.timeDelay == 0)
10875 if (appData.timeDelay < 0)
10877 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10886 int fromX, fromY, toX, toY;
10888 if (appData.debugMode) {
10889 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10892 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10895 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10896 pvInfoList[currentMove].depth = programStats.depth;
10897 pvInfoList[currentMove].score = programStats.score;
10898 pvInfoList[currentMove].time = 0;
10899 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10902 if (currentMove >= forwardMostMove) {
10903 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10904 // gameMode = EndOfGame;
10905 // ModeHighlight();
10907 /* [AS] Clear current move marker at the end of a game */
10908 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10913 toX = moveList[currentMove][2] - AAA;
10914 toY = moveList[currentMove][3] - ONE;
10916 if (moveList[currentMove][1] == '@') {
10917 if (appData.highlightLastMove) {
10918 SetHighlights(-1, -1, toX, toY);
10921 fromX = moveList[currentMove][0] - AAA;
10922 fromY = moveList[currentMove][1] - ONE;
10924 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10926 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10928 if (appData.highlightLastMove) {
10929 SetHighlights(fromX, fromY, toX, toY);
10932 DisplayMove(currentMove);
10933 SendMoveToProgram(currentMove++, &first);
10934 DisplayBothClocks();
10935 DrawPosition(FALSE, boards[currentMove]);
10936 // [HGM] PV info: always display, routine tests if empty
10937 DisplayComment(currentMove - 1, commentList[currentMove]);
10943 LoadGameOneMove (ChessMove readAhead)
10945 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10946 char promoChar = NULLCHAR;
10947 ChessMove moveType;
10948 char move[MSG_SIZ];
10951 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10952 gameMode != AnalyzeMode && gameMode != Training) {
10957 yyboardindex = forwardMostMove;
10958 if (readAhead != EndOfFile) {
10959 moveType = readAhead;
10961 if (gameFileFP == NULL)
10963 moveType = (ChessMove) Myylex();
10967 switch (moveType) {
10969 if (appData.debugMode)
10970 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10973 /* append the comment but don't display it */
10974 AppendComment(currentMove, p, FALSE);
10977 case WhiteCapturesEnPassant:
10978 case BlackCapturesEnPassant:
10979 case WhitePromotion:
10980 case BlackPromotion:
10981 case WhiteNonPromotion:
10982 case BlackNonPromotion:
10984 case WhiteKingSideCastle:
10985 case WhiteQueenSideCastle:
10986 case BlackKingSideCastle:
10987 case BlackQueenSideCastle:
10988 case WhiteKingSideCastleWild:
10989 case WhiteQueenSideCastleWild:
10990 case BlackKingSideCastleWild:
10991 case BlackQueenSideCastleWild:
10993 case WhiteHSideCastleFR:
10994 case WhiteASideCastleFR:
10995 case BlackHSideCastleFR:
10996 case BlackASideCastleFR:
10998 if (appData.debugMode)
10999 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11000 fromX = currentMoveString[0] - AAA;
11001 fromY = currentMoveString[1] - ONE;
11002 toX = currentMoveString[2] - AAA;
11003 toY = currentMoveString[3] - ONE;
11004 promoChar = currentMoveString[4];
11009 if (appData.debugMode)
11010 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11011 fromX = moveType == WhiteDrop ?
11012 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11013 (int) CharToPiece(ToLower(currentMoveString[0]));
11015 toX = currentMoveString[2] - AAA;
11016 toY = currentMoveString[3] - ONE;
11022 case GameUnfinished:
11023 if (appData.debugMode)
11024 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11025 p = strchr(yy_text, '{');
11026 if (p == NULL) p = strchr(yy_text, '(');
11029 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11031 q = strchr(p, *p == '{' ? '}' : ')');
11032 if (q != NULL) *q = NULLCHAR;
11035 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11036 GameEnds(moveType, p, GE_FILE);
11038 if (cmailMsgLoaded) {
11040 flipView = WhiteOnMove(currentMove);
11041 if (moveType == GameUnfinished) flipView = !flipView;
11042 if (appData.debugMode)
11043 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11048 if (appData.debugMode)
11049 fprintf(debugFP, "Parser hit end of file\n");
11050 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11056 if (WhiteOnMove(currentMove)) {
11057 GameEnds(BlackWins, "Black mates", GE_FILE);
11059 GameEnds(WhiteWins, "White mates", GE_FILE);
11063 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11069 case MoveNumberOne:
11070 if (lastLoadGameStart == GNUChessGame) {
11071 /* GNUChessGames have numbers, but they aren't move numbers */
11072 if (appData.debugMode)
11073 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11074 yy_text, (int) moveType);
11075 return LoadGameOneMove(EndOfFile); /* tail recursion */
11077 /* else fall thru */
11082 /* Reached start of next game in file */
11083 if (appData.debugMode)
11084 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11085 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11091 if (WhiteOnMove(currentMove)) {
11092 GameEnds(BlackWins, "Black mates", GE_FILE);
11094 GameEnds(WhiteWins, "White mates", GE_FILE);
11098 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11104 case PositionDiagram: /* should not happen; ignore */
11105 case ElapsedTime: /* ignore */
11106 case NAG: /* ignore */
11107 if (appData.debugMode)
11108 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11109 yy_text, (int) moveType);
11110 return LoadGameOneMove(EndOfFile); /* tail recursion */
11113 if (appData.testLegality) {
11114 if (appData.debugMode)
11115 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11116 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11117 (forwardMostMove / 2) + 1,
11118 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11119 DisplayError(move, 0);
11122 if (appData.debugMode)
11123 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11124 yy_text, currentMoveString);
11125 fromX = currentMoveString[0] - AAA;
11126 fromY = currentMoveString[1] - ONE;
11127 toX = currentMoveString[2] - AAA;
11128 toY = currentMoveString[3] - ONE;
11129 promoChar = currentMoveString[4];
11133 case AmbiguousMove:
11134 if (appData.debugMode)
11135 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11136 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11137 (forwardMostMove / 2) + 1,
11138 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11139 DisplayError(move, 0);
11144 case ImpossibleMove:
11145 if (appData.debugMode)
11146 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11147 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11148 (forwardMostMove / 2) + 1,
11149 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11150 DisplayError(move, 0);
11156 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11157 DrawPosition(FALSE, boards[currentMove]);
11158 DisplayBothClocks();
11159 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11160 DisplayComment(currentMove - 1, commentList[currentMove]);
11162 (void) StopLoadGameTimer();
11164 cmailOldMove = forwardMostMove;
11167 /* currentMoveString is set as a side-effect of yylex */
11169 thinkOutput[0] = NULLCHAR;
11170 MakeMove(fromX, fromY, toX, toY, promoChar);
11171 currentMove = forwardMostMove;
11176 /* Load the nth game from the given file */
11178 LoadGameFromFile (char *filename, int n, char *title, int useList)
11183 if (strcmp(filename, "-") == 0) {
11187 f = fopen(filename, "rb");
11189 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11190 DisplayError(buf, errno);
11194 if (fseek(f, 0, 0) == -1) {
11195 /* f is not seekable; probably a pipe */
11198 if (useList && n == 0) {
11199 int error = GameListBuild(f);
11201 DisplayError(_("Cannot build game list"), error);
11202 } else if (!ListEmpty(&gameList) &&
11203 ((ListGame *) gameList.tailPred)->number > 1) {
11204 GameListPopUp(f, title);
11211 return LoadGame(f, n, title, FALSE);
11216 MakeRegisteredMove ()
11218 int fromX, fromY, toX, toY;
11220 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11221 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11224 if (appData.debugMode)
11225 fprintf(debugFP, "Restoring %s for game %d\n",
11226 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11228 thinkOutput[0] = NULLCHAR;
11229 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11230 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11231 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11232 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11233 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11234 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11235 MakeMove(fromX, fromY, toX, toY, promoChar);
11236 ShowMove(fromX, fromY, toX, toY);
11238 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11245 if (WhiteOnMove(currentMove)) {
11246 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11248 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11253 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11260 if (WhiteOnMove(currentMove)) {
11261 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11263 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11268 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11279 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11281 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11285 if (gameNumber > nCmailGames) {
11286 DisplayError(_("No more games in this message"), 0);
11289 if (f == lastLoadGameFP) {
11290 int offset = gameNumber - lastLoadGameNumber;
11292 cmailMsg[0] = NULLCHAR;
11293 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11294 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11295 nCmailMovesRegistered--;
11297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11298 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11299 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11302 if (! RegisterMove()) return FALSE;
11306 retVal = LoadGame(f, gameNumber, title, useList);
11308 /* Make move registered during previous look at this game, if any */
11309 MakeRegisteredMove();
11311 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11312 commentList[currentMove]
11313 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11314 DisplayComment(currentMove - 1, commentList[currentMove]);
11320 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11322 ReloadGame (int offset)
11324 int gameNumber = lastLoadGameNumber + offset;
11325 if (lastLoadGameFP == NULL) {
11326 DisplayError(_("No game has been loaded yet"), 0);
11329 if (gameNumber <= 0) {
11330 DisplayError(_("Can't back up any further"), 0);
11333 if (cmailMsgLoaded) {
11334 return CmailLoadGame(lastLoadGameFP, gameNumber,
11335 lastLoadGameTitle, lastLoadGameUseList);
11337 return LoadGame(lastLoadGameFP, gameNumber,
11338 lastLoadGameTitle, lastLoadGameUseList);
11342 int keys[EmptySquare+1];
11345 PositionMatches (Board b1, Board b2)
11348 switch(appData.searchMode) {
11349 case 1: return CompareWithRights(b1, b2);
11351 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11352 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11356 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11358 sum += keys[b1[r][f]] - keys[b2[r][f]];
11362 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11363 sum += keys[b1[r][f]] - keys[b2[r][f]];
11375 int pieceList[256], quickBoard[256];
11376 ChessSquare pieceType[256] = { EmptySquare };
11377 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11378 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11379 int soughtTotal, turn;
11380 Boolean epOK, flipSearch;
11383 unsigned char piece, to;
11386 #define DSIZE (250000)
11388 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11389 Move *moveDatabase = initialSpace;
11390 unsigned int movePtr, dataSize = DSIZE;
11393 MakePieceList (Board board, int *counts)
11395 int r, f, n=Q_PROMO, total=0;
11396 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11397 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11398 int sq = f + (r<<4);
11399 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11400 quickBoard[sq] = ++n;
11402 pieceType[n] = board[r][f];
11403 counts[board[r][f]]++;
11404 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11405 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11409 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11414 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11416 int sq = fromX + (fromY<<4);
11417 int piece = quickBoard[sq];
11418 quickBoard[sq] = 0;
11419 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11420 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11421 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11422 moveDatabase[movePtr++].piece = Q_WCASTL;
11423 quickBoard[sq] = piece;
11424 piece = quickBoard[from]; quickBoard[from] = 0;
11425 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11427 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11428 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11429 moveDatabase[movePtr++].piece = Q_BCASTL;
11430 quickBoard[sq] = piece;
11431 piece = quickBoard[from]; quickBoard[from] = 0;
11432 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11434 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11435 quickBoard[(fromY<<4)+toX] = 0;
11436 moveDatabase[movePtr].piece = Q_EP;
11437 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11438 moveDatabase[movePtr].to = sq;
11440 if(promoPiece != pieceType[piece]) {
11441 moveDatabase[movePtr++].piece = Q_PROMO;
11442 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11444 moveDatabase[movePtr].piece = piece;
11445 quickBoard[sq] = piece;
11450 PackGame (Board board)
11452 Move *newSpace = NULL;
11453 moveDatabase[movePtr].piece = 0; // terminate previous game
11454 if(movePtr > dataSize) {
11455 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11456 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11457 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11460 Move *p = moveDatabase, *q = newSpace;
11461 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11462 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11463 moveDatabase = newSpace;
11464 } else { // calloc failed, we must be out of memory. Too bad...
11465 dataSize = 0; // prevent calloc events for all subsequent games
11466 return 0; // and signal this one isn't cached
11470 MakePieceList(board, counts);
11475 QuickCompare (Board board, int *minCounts, int *maxCounts)
11476 { // compare according to search mode
11478 switch(appData.searchMode)
11480 case 1: // exact position match
11481 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11482 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11483 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11486 case 2: // can have extra material on empty squares
11487 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11488 if(board[r][f] == EmptySquare) continue;
11489 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11492 case 3: // material with exact Pawn structure
11493 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11494 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11495 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11496 } // fall through to material comparison
11497 case 4: // exact material
11498 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11500 case 6: // material range with given imbalance
11501 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11502 // fall through to range comparison
11503 case 5: // material range
11504 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11510 QuickScan (Board board, Move *move)
11511 { // reconstruct game,and compare all positions in it
11512 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11514 int piece = move->piece;
11515 int to = move->to, from = pieceList[piece];
11516 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11517 if(!piece) return -1;
11518 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11519 piece = (++move)->piece;
11520 from = pieceList[piece];
11521 counts[pieceType[piece]]--;
11522 pieceType[piece] = (ChessSquare) move->to;
11523 counts[move->to]++;
11524 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11525 counts[pieceType[quickBoard[to]]]--;
11526 quickBoard[to] = 0; total--;
11529 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11530 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11531 from = pieceList[piece]; // so this must be King
11532 quickBoard[from] = 0;
11533 quickBoard[to] = piece;
11534 pieceList[piece] = to;
11539 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11540 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11541 quickBoard[from] = 0;
11542 quickBoard[to] = piece;
11543 pieceList[piece] = to;
11545 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11546 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11547 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11548 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11550 static int lastCounts[EmptySquare+1];
11552 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11553 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11554 } else stretch = 0;
11555 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11564 flipSearch = FALSE;
11565 CopyBoard(soughtBoard, boards[currentMove]);
11566 soughtTotal = MakePieceList(soughtBoard, maxSought);
11567 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11568 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11569 CopyBoard(reverseBoard, boards[currentMove]);
11570 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11571 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11572 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11573 reverseBoard[r][f] = piece;
11575 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11576 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11577 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11578 || (boards[currentMove][CASTLING][2] == NoRights ||
11579 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11580 && (boards[currentMove][CASTLING][5] == NoRights ||
11581 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11584 CopyBoard(flipBoard, soughtBoard);
11585 CopyBoard(rotateBoard, reverseBoard);
11586 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11587 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11588 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11591 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11592 if(appData.searchMode >= 5) {
11593 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11594 MakePieceList(soughtBoard, minSought);
11595 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11597 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11598 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11601 GameInfo dummyInfo;
11604 GameContainsPosition (FILE *f, ListGame *lg)
11606 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11607 int fromX, fromY, toX, toY;
11609 static int initDone=FALSE;
11611 // weed out games based on numerical tag comparison
11612 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11613 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11614 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11615 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11617 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11620 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11621 else CopyBoard(boards[scratch], initialPosition); // default start position
11624 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11625 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11628 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11629 fseek(f, lg->offset, 0);
11632 yyboardindex = scratch;
11633 quickFlag = plyNr+1;
11638 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11644 if(plyNr) return -1; // after we have seen moves, this is for new game
11647 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11648 case ImpossibleMove:
11649 case WhiteWins: // game ends here with these four
11652 case GameUnfinished:
11656 if(appData.testLegality) return -1;
11657 case WhiteCapturesEnPassant:
11658 case BlackCapturesEnPassant:
11659 case WhitePromotion:
11660 case BlackPromotion:
11661 case WhiteNonPromotion:
11662 case BlackNonPromotion:
11664 case WhiteKingSideCastle:
11665 case WhiteQueenSideCastle:
11666 case BlackKingSideCastle:
11667 case BlackQueenSideCastle:
11668 case WhiteKingSideCastleWild:
11669 case WhiteQueenSideCastleWild:
11670 case BlackKingSideCastleWild:
11671 case BlackQueenSideCastleWild:
11672 case WhiteHSideCastleFR:
11673 case WhiteASideCastleFR:
11674 case BlackHSideCastleFR:
11675 case BlackASideCastleFR:
11676 fromX = currentMoveString[0] - AAA;
11677 fromY = currentMoveString[1] - ONE;
11678 toX = currentMoveString[2] - AAA;
11679 toY = currentMoveString[3] - ONE;
11680 promoChar = currentMoveString[4];
11684 fromX = next == WhiteDrop ?
11685 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11686 (int) CharToPiece(ToLower(currentMoveString[0]));
11688 toX = currentMoveString[2] - AAA;
11689 toY = currentMoveString[3] - ONE;
11693 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11695 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11696 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11697 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11698 if(appData.findMirror) {
11699 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11700 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11705 /* Load the nth game from open file f */
11707 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11711 int gn = gameNumber;
11712 ListGame *lg = NULL;
11713 int numPGNTags = 0;
11715 GameMode oldGameMode;
11716 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11718 if (appData.debugMode)
11719 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11721 if (gameMode == Training )
11722 SetTrainingModeOff();
11724 oldGameMode = gameMode;
11725 if (gameMode != BeginningOfGame) {
11726 Reset(FALSE, TRUE);
11730 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11731 fclose(lastLoadGameFP);
11735 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11738 fseek(f, lg->offset, 0);
11739 GameListHighlight(gameNumber);
11740 pos = lg->position;
11744 DisplayError(_("Game number out of range"), 0);
11749 if (fseek(f, 0, 0) == -1) {
11750 if (f == lastLoadGameFP ?
11751 gameNumber == lastLoadGameNumber + 1 :
11755 DisplayError(_("Can't seek on game file"), 0);
11760 lastLoadGameFP = f;
11761 lastLoadGameNumber = gameNumber;
11762 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11763 lastLoadGameUseList = useList;
11767 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11768 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11769 lg->gameInfo.black);
11771 } else if (*title != NULLCHAR) {
11772 if (gameNumber > 1) {
11773 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11776 DisplayTitle(title);
11780 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11781 gameMode = PlayFromGameFile;
11785 currentMove = forwardMostMove = backwardMostMove = 0;
11786 CopyBoard(boards[0], initialPosition);
11790 * Skip the first gn-1 games in the file.
11791 * Also skip over anything that precedes an identifiable
11792 * start of game marker, to avoid being confused by
11793 * garbage at the start of the file. Currently
11794 * recognized start of game markers are the move number "1",
11795 * the pattern "gnuchess .* game", the pattern
11796 * "^[#;%] [^ ]* game file", and a PGN tag block.
11797 * A game that starts with one of the latter two patterns
11798 * will also have a move number 1, possibly
11799 * following a position diagram.
11800 * 5-4-02: Let's try being more lenient and allowing a game to
11801 * start with an unnumbered move. Does that break anything?
11803 cm = lastLoadGameStart = EndOfFile;
11805 yyboardindex = forwardMostMove;
11806 cm = (ChessMove) Myylex();
11809 if (cmailMsgLoaded) {
11810 nCmailGames = CMAIL_MAX_GAMES - gn;
11813 DisplayError(_("Game not found in file"), 0);
11820 lastLoadGameStart = cm;
11823 case MoveNumberOne:
11824 switch (lastLoadGameStart) {
11829 case MoveNumberOne:
11831 gn--; /* count this game */
11832 lastLoadGameStart = cm;
11841 switch (lastLoadGameStart) {
11844 case MoveNumberOne:
11846 gn--; /* count this game */
11847 lastLoadGameStart = cm;
11850 lastLoadGameStart = cm; /* game counted already */
11858 yyboardindex = forwardMostMove;
11859 cm = (ChessMove) Myylex();
11860 } while (cm == PGNTag || cm == Comment);
11867 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11868 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11869 != CMAIL_OLD_RESULT) {
11871 cmailResult[ CMAIL_MAX_GAMES
11872 - gn - 1] = CMAIL_OLD_RESULT;
11878 /* Only a NormalMove can be at the start of a game
11879 * without a position diagram. */
11880 if (lastLoadGameStart == EndOfFile ) {
11882 lastLoadGameStart = MoveNumberOne;
11891 if (appData.debugMode)
11892 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11894 if (cm == XBoardGame) {
11895 /* Skip any header junk before position diagram and/or move 1 */
11897 yyboardindex = forwardMostMove;
11898 cm = (ChessMove) Myylex();
11900 if (cm == EndOfFile ||
11901 cm == GNUChessGame || cm == XBoardGame) {
11902 /* Empty game; pretend end-of-file and handle later */
11907 if (cm == MoveNumberOne || cm == PositionDiagram ||
11908 cm == PGNTag || cm == Comment)
11911 } else if (cm == GNUChessGame) {
11912 if (gameInfo.event != NULL) {
11913 free(gameInfo.event);
11915 gameInfo.event = StrSave(yy_text);
11918 startedFromSetupPosition = FALSE;
11919 while (cm == PGNTag) {
11920 if (appData.debugMode)
11921 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11922 err = ParsePGNTag(yy_text, &gameInfo);
11923 if (!err) numPGNTags++;
11925 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11926 if(gameInfo.variant != oldVariant) {
11927 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11928 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11929 InitPosition(TRUE);
11930 oldVariant = gameInfo.variant;
11931 if (appData.debugMode)
11932 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11936 if (gameInfo.fen != NULL) {
11937 Board initial_position;
11938 startedFromSetupPosition = TRUE;
11939 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11941 DisplayError(_("Bad FEN position in file"), 0);
11944 CopyBoard(boards[0], initial_position);
11945 if (blackPlaysFirst) {
11946 currentMove = forwardMostMove = backwardMostMove = 1;
11947 CopyBoard(boards[1], initial_position);
11948 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11949 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11950 timeRemaining[0][1] = whiteTimeRemaining;
11951 timeRemaining[1][1] = blackTimeRemaining;
11952 if (commentList[0] != NULL) {
11953 commentList[1] = commentList[0];
11954 commentList[0] = NULL;
11957 currentMove = forwardMostMove = backwardMostMove = 0;
11959 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11961 initialRulePlies = FENrulePlies;
11962 for( i=0; i< nrCastlingRights; i++ )
11963 initialRights[i] = initial_position[CASTLING][i];
11965 yyboardindex = forwardMostMove;
11966 free(gameInfo.fen);
11967 gameInfo.fen = NULL;
11970 yyboardindex = forwardMostMove;
11971 cm = (ChessMove) Myylex();
11973 /* Handle comments interspersed among the tags */
11974 while (cm == Comment) {
11976 if (appData.debugMode)
11977 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11979 AppendComment(currentMove, p, FALSE);
11980 yyboardindex = forwardMostMove;
11981 cm = (ChessMove) Myylex();
11985 /* don't rely on existence of Event tag since if game was
11986 * pasted from clipboard the Event tag may not exist
11988 if (numPGNTags > 0){
11990 if (gameInfo.variant == VariantNormal) {
11991 VariantClass v = StringToVariant(gameInfo.event);
11992 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11993 if(v < VariantShogi) gameInfo.variant = v;
11996 if( appData.autoDisplayTags ) {
11997 tags = PGNTags(&gameInfo);
11998 TagsPopUp(tags, CmailMsg());
12003 /* Make something up, but don't display it now */
12008 if (cm == PositionDiagram) {
12011 Board initial_position;
12013 if (appData.debugMode)
12014 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12016 if (!startedFromSetupPosition) {
12018 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12019 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12030 initial_position[i][j++] = CharToPiece(*p);
12033 while (*p == ' ' || *p == '\t' ||
12034 *p == '\n' || *p == '\r') p++;
12036 if (strncmp(p, "black", strlen("black"))==0)
12037 blackPlaysFirst = TRUE;
12039 blackPlaysFirst = FALSE;
12040 startedFromSetupPosition = TRUE;
12042 CopyBoard(boards[0], initial_position);
12043 if (blackPlaysFirst) {
12044 currentMove = forwardMostMove = backwardMostMove = 1;
12045 CopyBoard(boards[1], initial_position);
12046 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12047 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12048 timeRemaining[0][1] = whiteTimeRemaining;
12049 timeRemaining[1][1] = blackTimeRemaining;
12050 if (commentList[0] != NULL) {
12051 commentList[1] = commentList[0];
12052 commentList[0] = NULL;
12055 currentMove = forwardMostMove = backwardMostMove = 0;
12058 yyboardindex = forwardMostMove;
12059 cm = (ChessMove) Myylex();
12062 if (first.pr == NoProc) {
12063 StartChessProgram(&first);
12065 InitChessProgram(&first, FALSE);
12066 SendToProgram("force\n", &first);
12067 if (startedFromSetupPosition) {
12068 SendBoard(&first, forwardMostMove);
12069 if (appData.debugMode) {
12070 fprintf(debugFP, "Load Game\n");
12072 DisplayBothClocks();
12075 /* [HGM] server: flag to write setup moves in broadcast file as one */
12076 loadFlag = appData.suppressLoadMoves;
12078 while (cm == Comment) {
12080 if (appData.debugMode)
12081 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12083 AppendComment(currentMove, p, FALSE);
12084 yyboardindex = forwardMostMove;
12085 cm = (ChessMove) Myylex();
12088 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12089 cm == WhiteWins || cm == BlackWins ||
12090 cm == GameIsDrawn || cm == GameUnfinished) {
12091 DisplayMessage("", _("No moves in game"));
12092 if (cmailMsgLoaded) {
12093 if (appData.debugMode)
12094 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12098 DrawPosition(FALSE, boards[currentMove]);
12099 DisplayBothClocks();
12100 gameMode = EditGame;
12107 // [HGM] PV info: routine tests if comment empty
12108 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12109 DisplayComment(currentMove - 1, commentList[currentMove]);
12111 if (!matchMode && appData.timeDelay != 0)
12112 DrawPosition(FALSE, boards[currentMove]);
12114 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12115 programStats.ok_to_send = 1;
12118 /* if the first token after the PGN tags is a move
12119 * and not move number 1, retrieve it from the parser
12121 if (cm != MoveNumberOne)
12122 LoadGameOneMove(cm);
12124 /* load the remaining moves from the file */
12125 while (LoadGameOneMove(EndOfFile)) {
12126 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12127 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12130 /* rewind to the start of the game */
12131 currentMove = backwardMostMove;
12133 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12135 if (oldGameMode == AnalyzeFile ||
12136 oldGameMode == AnalyzeMode) {
12137 AnalyzeFileEvent();
12140 if (!matchMode && pos >= 0) {
12141 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12143 if (matchMode || appData.timeDelay == 0) {
12145 } else if (appData.timeDelay > 0) {
12146 AutoPlayGameLoop();
12149 if (appData.debugMode)
12150 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12152 loadFlag = 0; /* [HGM] true game starts */
12156 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12158 ReloadPosition (int offset)
12160 int positionNumber = lastLoadPositionNumber + offset;
12161 if (lastLoadPositionFP == NULL) {
12162 DisplayError(_("No position has been loaded yet"), 0);
12165 if (positionNumber <= 0) {
12166 DisplayError(_("Can't back up any further"), 0);
12169 return LoadPosition(lastLoadPositionFP, positionNumber,
12170 lastLoadPositionTitle);
12173 /* Load the nth position from the given file */
12175 LoadPositionFromFile (char *filename, int n, char *title)
12180 if (strcmp(filename, "-") == 0) {
12181 return LoadPosition(stdin, n, "stdin");
12183 f = fopen(filename, "rb");
12185 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12186 DisplayError(buf, errno);
12189 return LoadPosition(f, n, title);
12194 /* Load the nth position from the given open file, and close it */
12196 LoadPosition (FILE *f, int positionNumber, char *title)
12198 char *p, line[MSG_SIZ];
12199 Board initial_position;
12200 int i, j, fenMode, pn;
12202 if (gameMode == Training )
12203 SetTrainingModeOff();
12205 if (gameMode != BeginningOfGame) {
12206 Reset(FALSE, TRUE);
12208 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12209 fclose(lastLoadPositionFP);
12211 if (positionNumber == 0) positionNumber = 1;
12212 lastLoadPositionFP = f;
12213 lastLoadPositionNumber = positionNumber;
12214 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12215 if (first.pr == NoProc && !appData.noChessProgram) {
12216 StartChessProgram(&first);
12217 InitChessProgram(&first, FALSE);
12219 pn = positionNumber;
12220 if (positionNumber < 0) {
12221 /* Negative position number means to seek to that byte offset */
12222 if (fseek(f, -positionNumber, 0) == -1) {
12223 DisplayError(_("Can't seek on position file"), 0);
12228 if (fseek(f, 0, 0) == -1) {
12229 if (f == lastLoadPositionFP ?
12230 positionNumber == lastLoadPositionNumber + 1 :
12231 positionNumber == 1) {
12234 DisplayError(_("Can't seek on position file"), 0);
12239 /* See if this file is FEN or old-style xboard */
12240 if (fgets(line, MSG_SIZ, f) == NULL) {
12241 DisplayError(_("Position not found in file"), 0);
12244 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12245 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12248 if (fenMode || line[0] == '#') pn--;
12250 /* skip positions before number pn */
12251 if (fgets(line, MSG_SIZ, f) == NULL) {
12253 DisplayError(_("Position not found in file"), 0);
12256 if (fenMode || line[0] == '#') pn--;
12261 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12262 DisplayError(_("Bad FEN position in file"), 0);
12266 (void) fgets(line, MSG_SIZ, f);
12267 (void) fgets(line, MSG_SIZ, f);
12269 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12270 (void) fgets(line, MSG_SIZ, f);
12271 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12274 initial_position[i][j++] = CharToPiece(*p);
12278 blackPlaysFirst = FALSE;
12280 (void) fgets(line, MSG_SIZ, f);
12281 if (strncmp(line, "black", strlen("black"))==0)
12282 blackPlaysFirst = TRUE;
12285 startedFromSetupPosition = TRUE;
12287 CopyBoard(boards[0], initial_position);
12288 if (blackPlaysFirst) {
12289 currentMove = forwardMostMove = backwardMostMove = 1;
12290 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12291 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12292 CopyBoard(boards[1], initial_position);
12293 DisplayMessage("", _("Black to play"));
12295 currentMove = forwardMostMove = backwardMostMove = 0;
12296 DisplayMessage("", _("White to play"));
12298 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12299 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12300 SendToProgram("force\n", &first);
12301 SendBoard(&first, forwardMostMove);
12303 if (appData.debugMode) {
12305 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12306 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12307 fprintf(debugFP, "Load Position\n");
12310 if (positionNumber > 1) {
12311 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12312 DisplayTitle(line);
12314 DisplayTitle(title);
12316 gameMode = EditGame;
12319 timeRemaining[0][1] = whiteTimeRemaining;
12320 timeRemaining[1][1] = blackTimeRemaining;
12321 DrawPosition(FALSE, boards[currentMove]);
12328 CopyPlayerNameIntoFileName (char **dest, char *src)
12330 while (*src != NULLCHAR && *src != ',') {
12335 *(*dest)++ = *src++;
12341 DefaultFileName (char *ext)
12343 static char def[MSG_SIZ];
12346 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12348 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12350 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12352 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12359 /* Save the current game to the given file */
12361 SaveGameToFile (char *filename, int append)
12365 int result, i, t,tot=0;
12367 if (strcmp(filename, "-") == 0) {
12368 return SaveGame(stdout, 0, NULL);
12370 for(i=0; i<10; i++) { // upto 10 tries
12371 f = fopen(filename, append ? "a" : "w");
12372 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12373 if(f || errno != 13) break;
12374 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12378 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12379 DisplayError(buf, errno);
12382 safeStrCpy(buf, lastMsg, MSG_SIZ);
12383 DisplayMessage(_("Waiting for access to save file"), "");
12384 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12385 DisplayMessage(_("Saving game"), "");
12386 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12387 result = SaveGame(f, 0, NULL);
12388 DisplayMessage(buf, "");
12395 SavePart (char *str)
12397 static char buf[MSG_SIZ];
12400 p = strchr(str, ' ');
12401 if (p == NULL) return str;
12402 strncpy(buf, str, p - str);
12403 buf[p - str] = NULLCHAR;
12407 #define PGN_MAX_LINE 75
12409 #define PGN_SIDE_WHITE 0
12410 #define PGN_SIDE_BLACK 1
12413 FindFirstMoveOutOfBook (int side)
12417 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12418 int index = backwardMostMove;
12419 int has_book_hit = 0;
12421 if( (index % 2) != side ) {
12425 while( index < forwardMostMove ) {
12426 /* Check to see if engine is in book */
12427 int depth = pvInfoList[index].depth;
12428 int score = pvInfoList[index].score;
12434 else if( score == 0 && depth == 63 ) {
12435 in_book = 1; /* Zappa */
12437 else if( score == 2 && depth == 99 ) {
12438 in_book = 1; /* Abrok */
12441 has_book_hit += in_book;
12457 GetOutOfBookInfo (char * buf)
12461 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12463 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12464 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12468 if( oob[0] >= 0 || oob[1] >= 0 ) {
12469 for( i=0; i<2; i++ ) {
12473 if( i > 0 && oob[0] >= 0 ) {
12474 strcat( buf, " " );
12477 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12478 sprintf( buf+strlen(buf), "%s%.2f",
12479 pvInfoList[idx].score >= 0 ? "+" : "",
12480 pvInfoList[idx].score / 100.0 );
12486 /* Save game in PGN style and close the file */
12488 SaveGamePGN (FILE *f)
12490 int i, offset, linelen, newblock;
12494 int movelen, numlen, blank;
12495 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12497 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12499 tm = time((time_t *) NULL);
12501 PrintPGNTags(f, &gameInfo);
12503 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12505 if (backwardMostMove > 0 || startedFromSetupPosition) {
12506 char *fen = PositionToFEN(backwardMostMove, NULL);
12507 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12508 fprintf(f, "\n{--------------\n");
12509 PrintPosition(f, backwardMostMove);
12510 fprintf(f, "--------------}\n");
12514 /* [AS] Out of book annotation */
12515 if( appData.saveOutOfBookInfo ) {
12518 GetOutOfBookInfo( buf );
12520 if( buf[0] != '\0' ) {
12521 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12528 i = backwardMostMove;
12532 while (i < forwardMostMove) {
12533 /* Print comments preceding this move */
12534 if (commentList[i] != NULL) {
12535 if (linelen > 0) fprintf(f, "\n");
12536 fprintf(f, "%s", commentList[i]);
12541 /* Format move number */
12543 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12546 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12548 numtext[0] = NULLCHAR;
12550 numlen = strlen(numtext);
12553 /* Print move number */
12554 blank = linelen > 0 && numlen > 0;
12555 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12564 fprintf(f, "%s", numtext);
12568 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12569 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12572 blank = linelen > 0 && movelen > 0;
12573 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12582 fprintf(f, "%s", move_buffer);
12583 linelen += movelen;
12585 /* [AS] Add PV info if present */
12586 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12587 /* [HGM] add time */
12588 char buf[MSG_SIZ]; int seconds;
12590 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12596 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12599 seconds = (seconds + 4)/10; // round to full seconds
12601 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12603 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12606 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12607 pvInfoList[i].score >= 0 ? "+" : "",
12608 pvInfoList[i].score / 100.0,
12609 pvInfoList[i].depth,
12612 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12614 /* Print score/depth */
12615 blank = linelen > 0 && movelen > 0;
12616 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12625 fprintf(f, "%s", move_buffer);
12626 linelen += movelen;
12632 /* Start a new line */
12633 if (linelen > 0) fprintf(f, "\n");
12635 /* Print comments after last move */
12636 if (commentList[i] != NULL) {
12637 fprintf(f, "%s\n", commentList[i]);
12641 if (gameInfo.resultDetails != NULL &&
12642 gameInfo.resultDetails[0] != NULLCHAR) {
12643 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12644 PGNResult(gameInfo.result));
12646 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12650 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12654 /* Save game in old style and close the file */
12656 SaveGameOldStyle (FILE *f)
12661 tm = time((time_t *) NULL);
12663 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12666 if (backwardMostMove > 0 || startedFromSetupPosition) {
12667 fprintf(f, "\n[--------------\n");
12668 PrintPosition(f, backwardMostMove);
12669 fprintf(f, "--------------]\n");
12674 i = backwardMostMove;
12675 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12677 while (i < forwardMostMove) {
12678 if (commentList[i] != NULL) {
12679 fprintf(f, "[%s]\n", commentList[i]);
12682 if ((i % 2) == 1) {
12683 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12686 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12688 if (commentList[i] != NULL) {
12692 if (i >= forwardMostMove) {
12696 fprintf(f, "%s\n", parseList[i]);
12701 if (commentList[i] != NULL) {
12702 fprintf(f, "[%s]\n", commentList[i]);
12705 /* This isn't really the old style, but it's close enough */
12706 if (gameInfo.resultDetails != NULL &&
12707 gameInfo.resultDetails[0] != NULLCHAR) {
12708 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12709 gameInfo.resultDetails);
12711 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12718 /* Save the current game to open file f and close the file */
12720 SaveGame (FILE *f, int dummy, char *dummy2)
12722 if (gameMode == EditPosition) EditPositionDone(TRUE);
12723 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12724 if (appData.oldSaveStyle)
12725 return SaveGameOldStyle(f);
12727 return SaveGamePGN(f);
12730 /* Save the current position to the given file */
12732 SavePositionToFile (char *filename)
12737 if (strcmp(filename, "-") == 0) {
12738 return SavePosition(stdout, 0, NULL);
12740 f = fopen(filename, "a");
12742 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12743 DisplayError(buf, errno);
12746 safeStrCpy(buf, lastMsg, MSG_SIZ);
12747 DisplayMessage(_("Waiting for access to save file"), "");
12748 flock(fileno(f), LOCK_EX); // [HGM] lock
12749 DisplayMessage(_("Saving position"), "");
12750 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12751 SavePosition(f, 0, NULL);
12752 DisplayMessage(buf, "");
12758 /* Save the current position to the given open file and close the file */
12760 SavePosition (FILE *f, int dummy, char *dummy2)
12765 if (gameMode == EditPosition) EditPositionDone(TRUE);
12766 if (appData.oldSaveStyle) {
12767 tm = time((time_t *) NULL);
12769 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12771 fprintf(f, "[--------------\n");
12772 PrintPosition(f, currentMove);
12773 fprintf(f, "--------------]\n");
12775 fen = PositionToFEN(currentMove, NULL);
12776 fprintf(f, "%s\n", fen);
12784 ReloadCmailMsgEvent (int unregister)
12787 static char *inFilename = NULL;
12788 static char *outFilename;
12790 struct stat inbuf, outbuf;
12793 /* Any registered moves are unregistered if unregister is set, */
12794 /* i.e. invoked by the signal handler */
12796 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12797 cmailMoveRegistered[i] = FALSE;
12798 if (cmailCommentList[i] != NULL) {
12799 free(cmailCommentList[i]);
12800 cmailCommentList[i] = NULL;
12803 nCmailMovesRegistered = 0;
12806 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12807 cmailResult[i] = CMAIL_NOT_RESULT;
12811 if (inFilename == NULL) {
12812 /* Because the filenames are static they only get malloced once */
12813 /* and they never get freed */
12814 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12815 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12817 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12818 sprintf(outFilename, "%s.out", appData.cmailGameName);
12821 status = stat(outFilename, &outbuf);
12823 cmailMailedMove = FALSE;
12825 status = stat(inFilename, &inbuf);
12826 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12829 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12830 counts the games, notes how each one terminated, etc.
12832 It would be nice to remove this kludge and instead gather all
12833 the information while building the game list. (And to keep it
12834 in the game list nodes instead of having a bunch of fixed-size
12835 parallel arrays.) Note this will require getting each game's
12836 termination from the PGN tags, as the game list builder does
12837 not process the game moves. --mann
12839 cmailMsgLoaded = TRUE;
12840 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12842 /* Load first game in the file or popup game menu */
12843 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12845 #endif /* !WIN32 */
12853 char string[MSG_SIZ];
12855 if ( cmailMailedMove
12856 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12857 return TRUE; /* Allow free viewing */
12860 /* Unregister move to ensure that we don't leave RegisterMove */
12861 /* with the move registered when the conditions for registering no */
12863 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12864 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12865 nCmailMovesRegistered --;
12867 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12869 free(cmailCommentList[lastLoadGameNumber - 1]);
12870 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12874 if (cmailOldMove == -1) {
12875 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12879 if (currentMove > cmailOldMove + 1) {
12880 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12884 if (currentMove < cmailOldMove) {
12885 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12889 if (forwardMostMove > currentMove) {
12890 /* Silently truncate extra moves */
12894 if ( (currentMove == cmailOldMove + 1)
12895 || ( (currentMove == cmailOldMove)
12896 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12897 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12898 if (gameInfo.result != GameUnfinished) {
12899 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12902 if (commentList[currentMove] != NULL) {
12903 cmailCommentList[lastLoadGameNumber - 1]
12904 = StrSave(commentList[currentMove]);
12906 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12908 if (appData.debugMode)
12909 fprintf(debugFP, "Saving %s for game %d\n",
12910 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12912 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12914 f = fopen(string, "w");
12915 if (appData.oldSaveStyle) {
12916 SaveGameOldStyle(f); /* also closes the file */
12918 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12919 f = fopen(string, "w");
12920 SavePosition(f, 0, NULL); /* also closes the file */
12922 fprintf(f, "{--------------\n");
12923 PrintPosition(f, currentMove);
12924 fprintf(f, "--------------}\n\n");
12926 SaveGame(f, 0, NULL); /* also closes the file*/
12929 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12930 nCmailMovesRegistered ++;
12931 } else if (nCmailGames == 1) {
12932 DisplayError(_("You have not made a move yet"), 0);
12943 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12944 FILE *commandOutput;
12945 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12946 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12952 if (! cmailMsgLoaded) {
12953 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12957 if (nCmailGames == nCmailResults) {
12958 DisplayError(_("No unfinished games"), 0);
12962 #if CMAIL_PROHIBIT_REMAIL
12963 if (cmailMailedMove) {
12964 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);
12965 DisplayError(msg, 0);
12970 if (! (cmailMailedMove || RegisterMove())) return;
12972 if ( cmailMailedMove
12973 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12974 snprintf(string, MSG_SIZ, partCommandString,
12975 appData.debugMode ? " -v" : "", appData.cmailGameName);
12976 commandOutput = popen(string, "r");
12978 if (commandOutput == NULL) {
12979 DisplayError(_("Failed to invoke cmail"), 0);
12981 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12982 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12984 if (nBuffers > 1) {
12985 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12986 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12987 nBytes = MSG_SIZ - 1;
12989 (void) memcpy(msg, buffer, nBytes);
12991 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12993 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12994 cmailMailedMove = TRUE; /* Prevent >1 moves */
12997 for (i = 0; i < nCmailGames; i ++) {
12998 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13003 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13005 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13007 appData.cmailGameName,
13009 LoadGameFromFile(buffer, 1, buffer, FALSE);
13010 cmailMsgLoaded = FALSE;
13014 DisplayInformation(msg);
13015 pclose(commandOutput);
13018 if ((*cmailMsg) != '\0') {
13019 DisplayInformation(cmailMsg);
13024 #endif /* !WIN32 */
13033 int prependComma = 0;
13035 char string[MSG_SIZ]; /* Space for game-list */
13038 if (!cmailMsgLoaded) return "";
13040 if (cmailMailedMove) {
13041 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13043 /* Create a list of games left */
13044 snprintf(string, MSG_SIZ, "[");
13045 for (i = 0; i < nCmailGames; i ++) {
13046 if (! ( cmailMoveRegistered[i]
13047 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13048 if (prependComma) {
13049 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13051 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13055 strcat(string, number);
13058 strcat(string, "]");
13060 if (nCmailMovesRegistered + nCmailResults == 0) {
13061 switch (nCmailGames) {
13063 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13067 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13071 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13076 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13078 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13083 if (nCmailResults == nCmailGames) {
13084 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13086 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13091 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13103 if (gameMode == Training)
13104 SetTrainingModeOff();
13107 cmailMsgLoaded = FALSE;
13108 if (appData.icsActive) {
13109 SendToICS(ics_prefix);
13110 SendToICS("refresh\n");
13115 ExitEvent (int status)
13119 /* Give up on clean exit */
13123 /* Keep trying for clean exit */
13127 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13129 if (telnetISR != NULL) {
13130 RemoveInputSource(telnetISR);
13132 if (icsPR != NoProc) {
13133 DestroyChildProcess(icsPR, TRUE);
13136 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13137 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13139 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13140 /* make sure this other one finishes before killing it! */
13141 if(endingGame) { int count = 0;
13142 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13143 while(endingGame && count++ < 10) DoSleep(1);
13144 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13147 /* Kill off chess programs */
13148 if (first.pr != NoProc) {
13151 DoSleep( appData.delayBeforeQuit );
13152 SendToProgram("quit\n", &first);
13153 DoSleep( appData.delayAfterQuit );
13154 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13156 if (second.pr != NoProc) {
13157 DoSleep( appData.delayBeforeQuit );
13158 SendToProgram("quit\n", &second);
13159 DoSleep( appData.delayAfterQuit );
13160 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13162 if (first.isr != NULL) {
13163 RemoveInputSource(first.isr);
13165 if (second.isr != NULL) {
13166 RemoveInputSource(second.isr);
13169 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13170 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13172 ShutDownFrontEnd();
13179 if (appData.debugMode)
13180 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13184 if (gameMode == MachinePlaysWhite ||
13185 gameMode == MachinePlaysBlack) {
13188 DisplayBothClocks();
13190 if (gameMode == PlayFromGameFile) {
13191 if (appData.timeDelay >= 0)
13192 AutoPlayGameLoop();
13193 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13194 Reset(FALSE, TRUE);
13195 SendToICS(ics_prefix);
13196 SendToICS("refresh\n");
13197 } else if (currentMove < forwardMostMove) {
13198 ForwardInner(forwardMostMove);
13200 pauseExamInvalid = FALSE;
13202 switch (gameMode) {
13206 pauseExamForwardMostMove = forwardMostMove;
13207 pauseExamInvalid = FALSE;
13210 case IcsPlayingWhite:
13211 case IcsPlayingBlack:
13215 case PlayFromGameFile:
13216 (void) StopLoadGameTimer();
13220 case BeginningOfGame:
13221 if (appData.icsActive) return;
13222 /* else fall through */
13223 case MachinePlaysWhite:
13224 case MachinePlaysBlack:
13225 case TwoMachinesPlay:
13226 if (forwardMostMove == 0)
13227 return; /* don't pause if no one has moved */
13228 if ((gameMode == MachinePlaysWhite &&
13229 !WhiteOnMove(forwardMostMove)) ||
13230 (gameMode == MachinePlaysBlack &&
13231 WhiteOnMove(forwardMostMove))) {
13242 EditCommentEvent ()
13244 char title[MSG_SIZ];
13246 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13247 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13249 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13250 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13251 parseList[currentMove - 1]);
13254 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13261 char *tags = PGNTags(&gameInfo);
13263 EditTagsPopUp(tags, NULL);
13268 AnalyzeModeEvent ()
13270 if (appData.noChessProgram || gameMode == AnalyzeMode)
13273 if (gameMode != AnalyzeFile) {
13274 if (!appData.icsEngineAnalyze) {
13276 if (gameMode != EditGame) return;
13278 ResurrectChessProgram();
13279 SendToProgram("analyze\n", &first);
13280 first.analyzing = TRUE;
13281 /*first.maybeThinking = TRUE;*/
13282 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13283 EngineOutputPopUp();
13285 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13290 StartAnalysisClock();
13291 GetTimeMark(&lastNodeCountTime);
13296 AnalyzeFileEvent ()
13298 if (appData.noChessProgram || gameMode == AnalyzeFile)
13301 if (gameMode != AnalyzeMode) {
13303 if (gameMode != EditGame) return;
13304 ResurrectChessProgram();
13305 SendToProgram("analyze\n", &first);
13306 first.analyzing = TRUE;
13307 /*first.maybeThinking = TRUE;*/
13308 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13309 EngineOutputPopUp();
13311 gameMode = AnalyzeFile;
13316 StartAnalysisClock();
13317 GetTimeMark(&lastNodeCountTime);
13319 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13323 MachineWhiteEvent ()
13326 char *bookHit = NULL;
13328 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13332 if (gameMode == PlayFromGameFile ||
13333 gameMode == TwoMachinesPlay ||
13334 gameMode == Training ||
13335 gameMode == AnalyzeMode ||
13336 gameMode == EndOfGame)
13339 if (gameMode == EditPosition)
13340 EditPositionDone(TRUE);
13342 if (!WhiteOnMove(currentMove)) {
13343 DisplayError(_("It is not White's turn"), 0);
13347 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13350 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13351 gameMode == AnalyzeFile)
13354 ResurrectChessProgram(); /* in case it isn't running */
13355 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13356 gameMode = MachinePlaysWhite;
13359 gameMode = MachinePlaysWhite;
13363 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13365 if (first.sendName) {
13366 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13367 SendToProgram(buf, &first);
13369 if (first.sendTime) {
13370 if (first.useColors) {
13371 SendToProgram("black\n", &first); /*gnu kludge*/
13373 SendTimeRemaining(&first, TRUE);
13375 if (first.useColors) {
13376 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13378 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13379 SetMachineThinkingEnables();
13380 first.maybeThinking = TRUE;
13384 if (appData.autoFlipView && !flipView) {
13385 flipView = !flipView;
13386 DrawPosition(FALSE, NULL);
13387 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13390 if(bookHit) { // [HGM] book: simulate book reply
13391 static char bookMove[MSG_SIZ]; // a bit generous?
13393 programStats.nodes = programStats.depth = programStats.time =
13394 programStats.score = programStats.got_only_move = 0;
13395 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13397 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13398 strcat(bookMove, bookHit);
13399 HandleMachineMove(bookMove, &first);
13404 MachineBlackEvent ()
13407 char *bookHit = NULL;
13409 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13413 if (gameMode == PlayFromGameFile ||
13414 gameMode == TwoMachinesPlay ||
13415 gameMode == Training ||
13416 gameMode == AnalyzeMode ||
13417 gameMode == EndOfGame)
13420 if (gameMode == EditPosition)
13421 EditPositionDone(TRUE);
13423 if (WhiteOnMove(currentMove)) {
13424 DisplayError(_("It is not Black's turn"), 0);
13428 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13431 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13432 gameMode == AnalyzeFile)
13435 ResurrectChessProgram(); /* in case it isn't running */
13436 gameMode = MachinePlaysBlack;
13440 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13442 if (first.sendName) {
13443 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13444 SendToProgram(buf, &first);
13446 if (first.sendTime) {
13447 if (first.useColors) {
13448 SendToProgram("white\n", &first); /*gnu kludge*/
13450 SendTimeRemaining(&first, FALSE);
13452 if (first.useColors) {
13453 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13455 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13456 SetMachineThinkingEnables();
13457 first.maybeThinking = TRUE;
13460 if (appData.autoFlipView && flipView) {
13461 flipView = !flipView;
13462 DrawPosition(FALSE, NULL);
13463 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13465 if(bookHit) { // [HGM] book: simulate book reply
13466 static char bookMove[MSG_SIZ]; // a bit generous?
13468 programStats.nodes = programStats.depth = programStats.time =
13469 programStats.score = programStats.got_only_move = 0;
13470 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13472 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13473 strcat(bookMove, bookHit);
13474 HandleMachineMove(bookMove, &first);
13480 DisplayTwoMachinesTitle ()
13483 if (appData.matchGames > 0) {
13484 if(appData.tourneyFile[0]) {
13485 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13486 gameInfo.white, _("vs."), gameInfo.black,
13487 nextGame+1, appData.matchGames+1,
13488 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13490 if (first.twoMachinesColor[0] == 'w') {
13491 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13492 gameInfo.white, _("vs."), gameInfo.black,
13493 first.matchWins, second.matchWins,
13494 matchGame - 1 - (first.matchWins + second.matchWins));
13496 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13497 gameInfo.white, _("vs."), gameInfo.black,
13498 second.matchWins, first.matchWins,
13499 matchGame - 1 - (first.matchWins + second.matchWins));
13502 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13508 SettingsMenuIfReady ()
13510 if (second.lastPing != second.lastPong) {
13511 DisplayMessage("", _("Waiting for second chess program"));
13512 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13516 DisplayMessage("", "");
13517 SettingsPopUp(&second);
13521 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13524 if (cps->pr == NoProc) {
13525 StartChessProgram(cps);
13526 if (cps->protocolVersion == 1) {
13529 /* kludge: allow timeout for initial "feature" command */
13531 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13532 DisplayMessage("", buf);
13533 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13541 TwoMachinesEvent P((void))
13545 ChessProgramState *onmove;
13546 char *bookHit = NULL;
13547 static int stalling = 0;
13551 if (appData.noChessProgram) return;
13553 switch (gameMode) {
13554 case TwoMachinesPlay:
13556 case MachinePlaysWhite:
13557 case MachinePlaysBlack:
13558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13563 case BeginningOfGame:
13564 case PlayFromGameFile:
13567 if (gameMode != EditGame) return;
13570 EditPositionDone(TRUE);
13581 // forwardMostMove = currentMove;
13582 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13584 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13586 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13587 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13588 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13592 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13593 SendToProgram("force\n", &second);
13595 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13598 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13599 if(appData.matchPause>10000 || appData.matchPause<10)
13600 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13601 wait = SubtractTimeMarks(&now, &pauseStart);
13602 if(wait < appData.matchPause) {
13603 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13606 // we are now committed to starting the game
13608 DisplayMessage("", "");
13609 if (startedFromSetupPosition) {
13610 SendBoard(&second, backwardMostMove);
13611 if (appData.debugMode) {
13612 fprintf(debugFP, "Two Machines\n");
13615 for (i = backwardMostMove; i < forwardMostMove; i++) {
13616 SendMoveToProgram(i, &second);
13619 gameMode = TwoMachinesPlay;
13621 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13623 DisplayTwoMachinesTitle();
13625 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13630 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13631 SendToProgram(first.computerString, &first);
13632 if (first.sendName) {
13633 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13634 SendToProgram(buf, &first);
13636 SendToProgram(second.computerString, &second);
13637 if (second.sendName) {
13638 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13639 SendToProgram(buf, &second);
13643 if (!first.sendTime || !second.sendTime) {
13644 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13645 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13647 if (onmove->sendTime) {
13648 if (onmove->useColors) {
13649 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13651 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13653 if (onmove->useColors) {
13654 SendToProgram(onmove->twoMachinesColor, onmove);
13656 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13657 // SendToProgram("go\n", onmove);
13658 onmove->maybeThinking = TRUE;
13659 SetMachineThinkingEnables();
13663 if(bookHit) { // [HGM] book: simulate book reply
13664 static char bookMove[MSG_SIZ]; // a bit generous?
13666 programStats.nodes = programStats.depth = programStats.time =
13667 programStats.score = programStats.got_only_move = 0;
13668 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13670 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13671 strcat(bookMove, bookHit);
13672 savedMessage = bookMove; // args for deferred call
13673 savedState = onmove;
13674 ScheduleDelayedEvent(DeferredBookMove, 1);
13681 if (gameMode == Training) {
13682 SetTrainingModeOff();
13683 gameMode = PlayFromGameFile;
13684 DisplayMessage("", _("Training mode off"));
13686 gameMode = Training;
13687 animateTraining = appData.animate;
13689 /* make sure we are not already at the end of the game */
13690 if (currentMove < forwardMostMove) {
13691 SetTrainingModeOn();
13692 DisplayMessage("", _("Training mode on"));
13694 gameMode = PlayFromGameFile;
13695 DisplayError(_("Already at end of game"), 0);
13704 if (!appData.icsActive) return;
13705 switch (gameMode) {
13706 case IcsPlayingWhite:
13707 case IcsPlayingBlack:
13710 case BeginningOfGame:
13718 EditPositionDone(TRUE);
13731 gameMode = IcsIdle;
13741 switch (gameMode) {
13743 SetTrainingModeOff();
13745 case MachinePlaysWhite:
13746 case MachinePlaysBlack:
13747 case BeginningOfGame:
13748 SendToProgram("force\n", &first);
13749 SetUserThinkingEnables();
13751 case PlayFromGameFile:
13752 (void) StopLoadGameTimer();
13753 if (gameFileFP != NULL) {
13758 EditPositionDone(TRUE);
13763 SendToProgram("force\n", &first);
13765 case TwoMachinesPlay:
13766 GameEnds(EndOfFile, NULL, GE_PLAYER);
13767 ResurrectChessProgram();
13768 SetUserThinkingEnables();
13771 ResurrectChessProgram();
13773 case IcsPlayingBlack:
13774 case IcsPlayingWhite:
13775 DisplayError(_("Warning: You are still playing a game"), 0);
13778 DisplayError(_("Warning: You are still observing a game"), 0);
13781 DisplayError(_("Warning: You are still examining a game"), 0);
13792 first.offeredDraw = second.offeredDraw = 0;
13794 if (gameMode == PlayFromGameFile) {
13795 whiteTimeRemaining = timeRemaining[0][currentMove];
13796 blackTimeRemaining = timeRemaining[1][currentMove];
13800 if (gameMode == MachinePlaysWhite ||
13801 gameMode == MachinePlaysBlack ||
13802 gameMode == TwoMachinesPlay ||
13803 gameMode == EndOfGame) {
13804 i = forwardMostMove;
13805 while (i > currentMove) {
13806 SendToProgram("undo\n", &first);
13809 if(!adjustedClock) {
13810 whiteTimeRemaining = timeRemaining[0][currentMove];
13811 blackTimeRemaining = timeRemaining[1][currentMove];
13812 DisplayBothClocks();
13814 if (whiteFlag || blackFlag) {
13815 whiteFlag = blackFlag = 0;
13820 gameMode = EditGame;
13827 EditPositionEvent ()
13829 if (gameMode == EditPosition) {
13835 if (gameMode != EditGame) return;
13837 gameMode = EditPosition;
13840 if (currentMove > 0)
13841 CopyBoard(boards[0], boards[currentMove]);
13843 blackPlaysFirst = !WhiteOnMove(currentMove);
13845 currentMove = forwardMostMove = backwardMostMove = 0;
13846 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13848 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13854 /* [DM] icsEngineAnalyze - possible call from other functions */
13855 if (appData.icsEngineAnalyze) {
13856 appData.icsEngineAnalyze = FALSE;
13858 DisplayMessage("",_("Close ICS engine analyze..."));
13860 if (first.analysisSupport && first.analyzing) {
13861 SendToProgram("exit\n", &first);
13862 first.analyzing = FALSE;
13864 thinkOutput[0] = NULLCHAR;
13868 EditPositionDone (Boolean fakeRights)
13870 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13872 startedFromSetupPosition = TRUE;
13873 InitChessProgram(&first, FALSE);
13874 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13875 boards[0][EP_STATUS] = EP_NONE;
13876 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13877 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13878 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13879 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13880 } else boards[0][CASTLING][2] = NoRights;
13881 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13882 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13883 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13884 } else boards[0][CASTLING][5] = NoRights;
13886 SendToProgram("force\n", &first);
13887 if (blackPlaysFirst) {
13888 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13889 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13890 currentMove = forwardMostMove = backwardMostMove = 1;
13891 CopyBoard(boards[1], boards[0]);
13893 currentMove = forwardMostMove = backwardMostMove = 0;
13895 SendBoard(&first, forwardMostMove);
13896 if (appData.debugMode) {
13897 fprintf(debugFP, "EditPosDone\n");
13900 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13901 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13902 gameMode = EditGame;
13904 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13905 ClearHighlights(); /* [AS] */
13908 /* Pause for `ms' milliseconds */
13909 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13911 TimeDelay (long ms)
13918 } while (SubtractTimeMarks(&m2, &m1) < ms);
13921 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13923 SendMultiLineToICS (char *buf)
13925 char temp[MSG_SIZ+1], *p;
13932 strncpy(temp, buf, len);
13937 if (*p == '\n' || *p == '\r')
13942 strcat(temp, "\n");
13944 SendToPlayer(temp, strlen(temp));
13948 SetWhiteToPlayEvent ()
13950 if (gameMode == EditPosition) {
13951 blackPlaysFirst = FALSE;
13952 DisplayBothClocks(); /* works because currentMove is 0 */
13953 } else if (gameMode == IcsExamining) {
13954 SendToICS(ics_prefix);
13955 SendToICS("tomove white\n");
13960 SetBlackToPlayEvent ()
13962 if (gameMode == EditPosition) {
13963 blackPlaysFirst = TRUE;
13964 currentMove = 1; /* kludge */
13965 DisplayBothClocks();
13967 } else if (gameMode == IcsExamining) {
13968 SendToICS(ics_prefix);
13969 SendToICS("tomove black\n");
13974 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13977 ChessSquare piece = boards[0][y][x];
13979 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13981 switch (selection) {
13983 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13984 SendToICS(ics_prefix);
13985 SendToICS("bsetup clear\n");
13986 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13987 SendToICS(ics_prefix);
13988 SendToICS("clearboard\n");
13990 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13991 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13992 for (y = 0; y < BOARD_HEIGHT; y++) {
13993 if (gameMode == IcsExamining) {
13994 if (boards[currentMove][y][x] != EmptySquare) {
13995 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14000 boards[0][y][x] = p;
14005 if (gameMode == EditPosition) {
14006 DrawPosition(FALSE, boards[0]);
14011 SetWhiteToPlayEvent();
14015 SetBlackToPlayEvent();
14019 if (gameMode == IcsExamining) {
14020 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14021 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14024 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14025 if(x == BOARD_LEFT-2) {
14026 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14027 boards[0][y][1] = 0;
14029 if(x == BOARD_RGHT+1) {
14030 if(y >= gameInfo.holdingsSize) break;
14031 boards[0][y][BOARD_WIDTH-2] = 0;
14034 boards[0][y][x] = EmptySquare;
14035 DrawPosition(FALSE, boards[0]);
14040 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14041 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14042 selection = (ChessSquare) (PROMOTED piece);
14043 } else if(piece == EmptySquare) selection = WhiteSilver;
14044 else selection = (ChessSquare)((int)piece - 1);
14048 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14049 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14050 selection = (ChessSquare) (DEMOTED piece);
14051 } else if(piece == EmptySquare) selection = BlackSilver;
14052 else selection = (ChessSquare)((int)piece + 1);
14057 if(gameInfo.variant == VariantShatranj ||
14058 gameInfo.variant == VariantXiangqi ||
14059 gameInfo.variant == VariantCourier ||
14060 gameInfo.variant == VariantMakruk )
14061 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14066 if(gameInfo.variant == VariantXiangqi)
14067 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14068 if(gameInfo.variant == VariantKnightmate)
14069 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14072 if (gameMode == IcsExamining) {
14073 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14074 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14075 PieceToChar(selection), AAA + x, ONE + y);
14078 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14080 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14081 n = PieceToNumber(selection - BlackPawn);
14082 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14083 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14084 boards[0][BOARD_HEIGHT-1-n][1]++;
14086 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14087 n = PieceToNumber(selection);
14088 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14089 boards[0][n][BOARD_WIDTH-1] = selection;
14090 boards[0][n][BOARD_WIDTH-2]++;
14093 boards[0][y][x] = selection;
14094 DrawPosition(TRUE, boards[0]);
14096 fromX = fromY = -1;
14104 DropMenuEvent (ChessSquare selection, int x, int y)
14106 ChessMove moveType;
14108 switch (gameMode) {
14109 case IcsPlayingWhite:
14110 case MachinePlaysBlack:
14111 if (!WhiteOnMove(currentMove)) {
14112 DisplayMoveError(_("It is Black's turn"));
14115 moveType = WhiteDrop;
14117 case IcsPlayingBlack:
14118 case MachinePlaysWhite:
14119 if (WhiteOnMove(currentMove)) {
14120 DisplayMoveError(_("It is White's turn"));
14123 moveType = BlackDrop;
14126 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14132 if (moveType == BlackDrop && selection < BlackPawn) {
14133 selection = (ChessSquare) ((int) selection
14134 + (int) BlackPawn - (int) WhitePawn);
14136 if (boards[currentMove][y][x] != EmptySquare) {
14137 DisplayMoveError(_("That square is occupied"));
14141 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14147 /* Accept a pending offer of any kind from opponent */
14149 if (appData.icsActive) {
14150 SendToICS(ics_prefix);
14151 SendToICS("accept\n");
14152 } else if (cmailMsgLoaded) {
14153 if (currentMove == cmailOldMove &&
14154 commentList[cmailOldMove] != NULL &&
14155 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14156 "Black offers a draw" : "White offers a draw")) {
14158 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14159 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14161 DisplayError(_("There is no pending offer on this move"), 0);
14162 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14165 /* Not used for offers from chess program */
14172 /* Decline a pending offer of any kind from opponent */
14174 if (appData.icsActive) {
14175 SendToICS(ics_prefix);
14176 SendToICS("decline\n");
14177 } else if (cmailMsgLoaded) {
14178 if (currentMove == cmailOldMove &&
14179 commentList[cmailOldMove] != NULL &&
14180 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14181 "Black offers a draw" : "White offers a draw")) {
14183 AppendComment(cmailOldMove, "Draw declined", TRUE);
14184 DisplayComment(cmailOldMove - 1, "Draw declined");
14187 DisplayError(_("There is no pending offer on this move"), 0);
14190 /* Not used for offers from chess program */
14197 /* Issue ICS rematch command */
14198 if (appData.icsActive) {
14199 SendToICS(ics_prefix);
14200 SendToICS("rematch\n");
14207 /* Call your opponent's flag (claim a win on time) */
14208 if (appData.icsActive) {
14209 SendToICS(ics_prefix);
14210 SendToICS("flag\n");
14212 switch (gameMode) {
14215 case MachinePlaysWhite:
14218 GameEnds(GameIsDrawn, "Both players ran out of time",
14221 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14223 DisplayError(_("Your opponent is not out of time"), 0);
14226 case MachinePlaysBlack:
14229 GameEnds(GameIsDrawn, "Both players ran out of time",
14232 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14234 DisplayError(_("Your opponent is not out of time"), 0);
14242 ClockClick (int which)
14243 { // [HGM] code moved to back-end from winboard.c
14244 if(which) { // black clock
14245 if (gameMode == EditPosition || gameMode == IcsExamining) {
14246 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14247 SetBlackToPlayEvent();
14248 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14249 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14250 } else if (shiftKey) {
14251 AdjustClock(which, -1);
14252 } else if (gameMode == IcsPlayingWhite ||
14253 gameMode == MachinePlaysBlack) {
14256 } else { // white clock
14257 if (gameMode == EditPosition || gameMode == IcsExamining) {
14258 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14259 SetWhiteToPlayEvent();
14260 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14261 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14262 } else if (shiftKey) {
14263 AdjustClock(which, -1);
14264 } else if (gameMode == IcsPlayingBlack ||
14265 gameMode == MachinePlaysWhite) {
14274 /* Offer draw or accept pending draw offer from opponent */
14276 if (appData.icsActive) {
14277 /* Note: tournament rules require draw offers to be
14278 made after you make your move but before you punch
14279 your clock. Currently ICS doesn't let you do that;
14280 instead, you immediately punch your clock after making
14281 a move, but you can offer a draw at any time. */
14283 SendToICS(ics_prefix);
14284 SendToICS("draw\n");
14285 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14286 } else if (cmailMsgLoaded) {
14287 if (currentMove == cmailOldMove &&
14288 commentList[cmailOldMove] != NULL &&
14289 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14290 "Black offers a draw" : "White offers a draw")) {
14291 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14292 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14293 } else if (currentMove == cmailOldMove + 1) {
14294 char *offer = WhiteOnMove(cmailOldMove) ?
14295 "White offers a draw" : "Black offers a draw";
14296 AppendComment(currentMove, offer, TRUE);
14297 DisplayComment(currentMove - 1, offer);
14298 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14300 DisplayError(_("You must make your move before offering a draw"), 0);
14301 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14303 } else if (first.offeredDraw) {
14304 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14306 if (first.sendDrawOffers) {
14307 SendToProgram("draw\n", &first);
14308 userOfferedDraw = TRUE;
14316 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14318 if (appData.icsActive) {
14319 SendToICS(ics_prefix);
14320 SendToICS("adjourn\n");
14322 /* Currently GNU Chess doesn't offer or accept Adjourns */
14330 /* Offer Abort or accept pending Abort offer from opponent */
14332 if (appData.icsActive) {
14333 SendToICS(ics_prefix);
14334 SendToICS("abort\n");
14336 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14343 /* Resign. You can do this even if it's not your turn. */
14345 if (appData.icsActive) {
14346 SendToICS(ics_prefix);
14347 SendToICS("resign\n");
14349 switch (gameMode) {
14350 case MachinePlaysWhite:
14351 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14353 case MachinePlaysBlack:
14354 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14357 if (cmailMsgLoaded) {
14359 if (WhiteOnMove(cmailOldMove)) {
14360 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14362 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14364 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14375 StopObservingEvent ()
14377 /* Stop observing current games */
14378 SendToICS(ics_prefix);
14379 SendToICS("unobserve\n");
14383 StopExaminingEvent ()
14385 /* Stop observing current game */
14386 SendToICS(ics_prefix);
14387 SendToICS("unexamine\n");
14391 ForwardInner (int target)
14393 int limit; int oldSeekGraphUp = seekGraphUp;
14395 if (appData.debugMode)
14396 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14397 target, currentMove, forwardMostMove);
14399 if (gameMode == EditPosition)
14402 seekGraphUp = FALSE;
14403 MarkTargetSquares(1);
14405 if (gameMode == PlayFromGameFile && !pausing)
14408 if (gameMode == IcsExamining && pausing)
14409 limit = pauseExamForwardMostMove;
14411 limit = forwardMostMove;
14413 if (target > limit) target = limit;
14415 if (target > 0 && moveList[target - 1][0]) {
14416 int fromX, fromY, toX, toY;
14417 toX = moveList[target - 1][2] - AAA;
14418 toY = moveList[target - 1][3] - ONE;
14419 if (moveList[target - 1][1] == '@') {
14420 if (appData.highlightLastMove) {
14421 SetHighlights(-1, -1, toX, toY);
14424 fromX = moveList[target - 1][0] - AAA;
14425 fromY = moveList[target - 1][1] - ONE;
14426 if (target == currentMove + 1) {
14427 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14429 if (appData.highlightLastMove) {
14430 SetHighlights(fromX, fromY, toX, toY);
14434 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14435 gameMode == Training || gameMode == PlayFromGameFile ||
14436 gameMode == AnalyzeFile) {
14437 while (currentMove < target) {
14438 SendMoveToProgram(currentMove++, &first);
14441 currentMove = target;
14444 if (gameMode == EditGame || gameMode == EndOfGame) {
14445 whiteTimeRemaining = timeRemaining[0][currentMove];
14446 blackTimeRemaining = timeRemaining[1][currentMove];
14448 DisplayBothClocks();
14449 DisplayMove(currentMove - 1);
14450 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14451 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14452 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14453 DisplayComment(currentMove - 1, commentList[currentMove]);
14455 ClearMap(); // [HGM] exclude: invalidate map
14462 if (gameMode == IcsExamining && !pausing) {
14463 SendToICS(ics_prefix);
14464 SendToICS("forward\n");
14466 ForwardInner(currentMove + 1);
14473 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14474 /* to optimze, we temporarily turn off analysis mode while we feed
14475 * the remaining moves to the engine. Otherwise we get analysis output
14478 if (first.analysisSupport) {
14479 SendToProgram("exit\nforce\n", &first);
14480 first.analyzing = FALSE;
14484 if (gameMode == IcsExamining && !pausing) {
14485 SendToICS(ics_prefix);
14486 SendToICS("forward 999999\n");
14488 ForwardInner(forwardMostMove);
14491 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14492 /* we have fed all the moves, so reactivate analysis mode */
14493 SendToProgram("analyze\n", &first);
14494 first.analyzing = TRUE;
14495 /*first.maybeThinking = TRUE;*/
14496 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14501 BackwardInner (int target)
14503 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14505 if (appData.debugMode)
14506 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14507 target, currentMove, forwardMostMove);
14509 if (gameMode == EditPosition) return;
14510 seekGraphUp = FALSE;
14511 MarkTargetSquares(1);
14512 if (currentMove <= backwardMostMove) {
14514 DrawPosition(full_redraw, boards[currentMove]);
14517 if (gameMode == PlayFromGameFile && !pausing)
14520 if (moveList[target][0]) {
14521 int fromX, fromY, toX, toY;
14522 toX = moveList[target][2] - AAA;
14523 toY = moveList[target][3] - ONE;
14524 if (moveList[target][1] == '@') {
14525 if (appData.highlightLastMove) {
14526 SetHighlights(-1, -1, toX, toY);
14529 fromX = moveList[target][0] - AAA;
14530 fromY = moveList[target][1] - ONE;
14531 if (target == currentMove - 1) {
14532 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14534 if (appData.highlightLastMove) {
14535 SetHighlights(fromX, fromY, toX, toY);
14539 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14540 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14541 while (currentMove > target) {
14542 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14543 // null move cannot be undone. Reload program with move history before it.
14545 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14546 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14548 SendBoard(&first, i);
14549 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14552 SendToProgram("undo\n", &first);
14556 currentMove = target;
14559 if (gameMode == EditGame || gameMode == EndOfGame) {
14560 whiteTimeRemaining = timeRemaining[0][currentMove];
14561 blackTimeRemaining = timeRemaining[1][currentMove];
14563 DisplayBothClocks();
14564 DisplayMove(currentMove - 1);
14565 DrawPosition(full_redraw, boards[currentMove]);
14566 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14567 // [HGM] PV info: routine tests if comment empty
14568 DisplayComment(currentMove - 1, commentList[currentMove]);
14569 ClearMap(); // [HGM] exclude: invalidate map
14575 if (gameMode == IcsExamining && !pausing) {
14576 SendToICS(ics_prefix);
14577 SendToICS("backward\n");
14579 BackwardInner(currentMove - 1);
14586 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14587 /* to optimize, we temporarily turn off analysis mode while we undo
14588 * all the moves. Otherwise we get analysis output after each undo.
14590 if (first.analysisSupport) {
14591 SendToProgram("exit\nforce\n", &first);
14592 first.analyzing = FALSE;
14596 if (gameMode == IcsExamining && !pausing) {
14597 SendToICS(ics_prefix);
14598 SendToICS("backward 999999\n");
14600 BackwardInner(backwardMostMove);
14603 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14604 /* we have fed all the moves, so reactivate analysis mode */
14605 SendToProgram("analyze\n", &first);
14606 first.analyzing = TRUE;
14607 /*first.maybeThinking = TRUE;*/
14608 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14615 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14616 if (to >= forwardMostMove) to = forwardMostMove;
14617 if (to <= backwardMostMove) to = backwardMostMove;
14618 if (to < currentMove) {
14626 RevertEvent (Boolean annotate)
14628 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14631 if (gameMode != IcsExamining) {
14632 DisplayError(_("You are not examining a game"), 0);
14636 DisplayError(_("You can't revert while pausing"), 0);
14639 SendToICS(ics_prefix);
14640 SendToICS("revert\n");
14644 RetractMoveEvent ()
14646 switch (gameMode) {
14647 case MachinePlaysWhite:
14648 case MachinePlaysBlack:
14649 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14650 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14653 if (forwardMostMove < 2) return;
14654 currentMove = forwardMostMove = forwardMostMove - 2;
14655 whiteTimeRemaining = timeRemaining[0][currentMove];
14656 blackTimeRemaining = timeRemaining[1][currentMove];
14657 DisplayBothClocks();
14658 DisplayMove(currentMove - 1);
14659 ClearHighlights();/*!! could figure this out*/
14660 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14661 SendToProgram("remove\n", &first);
14662 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14665 case BeginningOfGame:
14669 case IcsPlayingWhite:
14670 case IcsPlayingBlack:
14671 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14672 SendToICS(ics_prefix);
14673 SendToICS("takeback 2\n");
14675 SendToICS(ics_prefix);
14676 SendToICS("takeback 1\n");
14685 ChessProgramState *cps;
14687 switch (gameMode) {
14688 case MachinePlaysWhite:
14689 if (!WhiteOnMove(forwardMostMove)) {
14690 DisplayError(_("It is your turn"), 0);
14695 case MachinePlaysBlack:
14696 if (WhiteOnMove(forwardMostMove)) {
14697 DisplayError(_("It is your turn"), 0);
14702 case TwoMachinesPlay:
14703 if (WhiteOnMove(forwardMostMove) ==
14704 (first.twoMachinesColor[0] == 'w')) {
14710 case BeginningOfGame:
14714 SendToProgram("?\n", cps);
14718 TruncateGameEvent ()
14721 if (gameMode != EditGame) return;
14728 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14729 if (forwardMostMove > currentMove) {
14730 if (gameInfo.resultDetails != NULL) {
14731 free(gameInfo.resultDetails);
14732 gameInfo.resultDetails = NULL;
14733 gameInfo.result = GameUnfinished;
14735 forwardMostMove = currentMove;
14736 HistorySet(parseList, backwardMostMove, forwardMostMove,
14744 if (appData.noChessProgram) return;
14745 switch (gameMode) {
14746 case MachinePlaysWhite:
14747 if (WhiteOnMove(forwardMostMove)) {
14748 DisplayError(_("Wait until your turn"), 0);
14752 case BeginningOfGame:
14753 case MachinePlaysBlack:
14754 if (!WhiteOnMove(forwardMostMove)) {
14755 DisplayError(_("Wait until your turn"), 0);
14760 DisplayError(_("No hint available"), 0);
14763 SendToProgram("hint\n", &first);
14764 hintRequested = TRUE;
14770 if (appData.noChessProgram) return;
14771 switch (gameMode) {
14772 case MachinePlaysWhite:
14773 if (WhiteOnMove(forwardMostMove)) {
14774 DisplayError(_("Wait until your turn"), 0);
14778 case BeginningOfGame:
14779 case MachinePlaysBlack:
14780 if (!WhiteOnMove(forwardMostMove)) {
14781 DisplayError(_("Wait until your turn"), 0);
14786 EditPositionDone(TRUE);
14788 case TwoMachinesPlay:
14793 SendToProgram("bk\n", &first);
14794 bookOutput[0] = NULLCHAR;
14795 bookRequested = TRUE;
14801 char *tags = PGNTags(&gameInfo);
14802 TagsPopUp(tags, CmailMsg());
14806 /* end button procedures */
14809 PrintPosition (FILE *fp, int move)
14813 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14814 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14815 char c = PieceToChar(boards[move][i][j]);
14816 fputc(c == 'x' ? '.' : c, fp);
14817 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14820 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14821 fprintf(fp, "white to play\n");
14823 fprintf(fp, "black to play\n");
14827 PrintOpponents (FILE *fp)
14829 if (gameInfo.white != NULL) {
14830 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14836 /* Find last component of program's own name, using some heuristics */
14838 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14841 int local = (strcmp(host, "localhost") == 0);
14842 while (!local && (p = strchr(prog, ';')) != NULL) {
14844 while (*p == ' ') p++;
14847 if (*prog == '"' || *prog == '\'') {
14848 q = strchr(prog + 1, *prog);
14850 q = strchr(prog, ' ');
14852 if (q == NULL) q = prog + strlen(prog);
14854 while (p >= prog && *p != '/' && *p != '\\') p--;
14856 if(p == prog && *p == '"') p++;
14858 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14859 memcpy(buf, p, q - p);
14860 buf[q - p] = NULLCHAR;
14868 TimeControlTagValue ()
14871 if (!appData.clockMode) {
14872 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14873 } else if (movesPerSession > 0) {
14874 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14875 } else if (timeIncrement == 0) {
14876 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14878 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14880 return StrSave(buf);
14886 /* This routine is used only for certain modes */
14887 VariantClass v = gameInfo.variant;
14888 ChessMove r = GameUnfinished;
14891 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14892 r = gameInfo.result;
14893 p = gameInfo.resultDetails;
14894 gameInfo.resultDetails = NULL;
14896 ClearGameInfo(&gameInfo);
14897 gameInfo.variant = v;
14899 switch (gameMode) {
14900 case MachinePlaysWhite:
14901 gameInfo.event = StrSave( appData.pgnEventHeader );
14902 gameInfo.site = StrSave(HostName());
14903 gameInfo.date = PGNDate();
14904 gameInfo.round = StrSave("-");
14905 gameInfo.white = StrSave(first.tidy);
14906 gameInfo.black = StrSave(UserName());
14907 gameInfo.timeControl = TimeControlTagValue();
14910 case MachinePlaysBlack:
14911 gameInfo.event = StrSave( appData.pgnEventHeader );
14912 gameInfo.site = StrSave(HostName());
14913 gameInfo.date = PGNDate();
14914 gameInfo.round = StrSave("-");
14915 gameInfo.white = StrSave(UserName());
14916 gameInfo.black = StrSave(first.tidy);
14917 gameInfo.timeControl = TimeControlTagValue();
14920 case TwoMachinesPlay:
14921 gameInfo.event = StrSave( appData.pgnEventHeader );
14922 gameInfo.site = StrSave(HostName());
14923 gameInfo.date = PGNDate();
14926 snprintf(buf, MSG_SIZ, "%d", roundNr);
14927 gameInfo.round = StrSave(buf);
14929 gameInfo.round = StrSave("-");
14931 if (first.twoMachinesColor[0] == 'w') {
14932 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14933 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14935 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14936 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14938 gameInfo.timeControl = TimeControlTagValue();
14942 gameInfo.event = StrSave("Edited game");
14943 gameInfo.site = StrSave(HostName());
14944 gameInfo.date = PGNDate();
14945 gameInfo.round = StrSave("-");
14946 gameInfo.white = StrSave("-");
14947 gameInfo.black = StrSave("-");
14948 gameInfo.result = r;
14949 gameInfo.resultDetails = p;
14953 gameInfo.event = StrSave("Edited position");
14954 gameInfo.site = StrSave(HostName());
14955 gameInfo.date = PGNDate();
14956 gameInfo.round = StrSave("-");
14957 gameInfo.white = StrSave("-");
14958 gameInfo.black = StrSave("-");
14961 case IcsPlayingWhite:
14962 case IcsPlayingBlack:
14967 case PlayFromGameFile:
14968 gameInfo.event = StrSave("Game from non-PGN file");
14969 gameInfo.site = StrSave(HostName());
14970 gameInfo.date = PGNDate();
14971 gameInfo.round = StrSave("-");
14972 gameInfo.white = StrSave("?");
14973 gameInfo.black = StrSave("?");
14982 ReplaceComment (int index, char *text)
14988 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14989 pvInfoList[index-1].depth == len &&
14990 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14991 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14992 while (*text == '\n') text++;
14993 len = strlen(text);
14994 while (len > 0 && text[len - 1] == '\n') len--;
14996 if (commentList[index] != NULL)
14997 free(commentList[index]);
15000 commentList[index] = NULL;
15003 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15004 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15005 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15006 commentList[index] = (char *) malloc(len + 2);
15007 strncpy(commentList[index], text, len);
15008 commentList[index][len] = '\n';
15009 commentList[index][len + 1] = NULLCHAR;
15011 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15013 commentList[index] = (char *) malloc(len + 7);
15014 safeStrCpy(commentList[index], "{\n", 3);
15015 safeStrCpy(commentList[index]+2, text, len+1);
15016 commentList[index][len+2] = NULLCHAR;
15017 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15018 strcat(commentList[index], "\n}\n");
15023 CrushCRs (char *text)
15031 if (ch == '\r') continue;
15033 } while (ch != '\0');
15037 AppendComment (int index, char *text, Boolean addBraces)
15038 /* addBraces tells if we should add {} */
15043 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15044 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15047 while (*text == '\n') text++;
15048 len = strlen(text);
15049 while (len > 0 && text[len - 1] == '\n') len--;
15050 text[len] = NULLCHAR;
15052 if (len == 0) return;
15054 if (commentList[index] != NULL) {
15055 Boolean addClosingBrace = addBraces;
15056 old = commentList[index];
15057 oldlen = strlen(old);
15058 while(commentList[index][oldlen-1] == '\n')
15059 commentList[index][--oldlen] = NULLCHAR;
15060 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15061 safeStrCpy(commentList[index], old, oldlen + len + 6);
15063 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15064 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15065 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15066 while (*text == '\n') { text++; len--; }
15067 commentList[index][--oldlen] = NULLCHAR;
15069 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15070 else strcat(commentList[index], "\n");
15071 strcat(commentList[index], text);
15072 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15073 else strcat(commentList[index], "\n");
15075 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15077 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15078 else commentList[index][0] = NULLCHAR;
15079 strcat(commentList[index], text);
15080 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15081 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15086 FindStr (char * text, char * sub_text)
15088 char * result = strstr( text, sub_text );
15090 if( result != NULL ) {
15091 result += strlen( sub_text );
15097 /* [AS] Try to extract PV info from PGN comment */
15098 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15100 GetInfoFromComment (int index, char * text)
15102 char * sep = text, *p;
15104 if( text != NULL && index > 0 ) {
15107 int time = -1, sec = 0, deci;
15108 char * s_eval = FindStr( text, "[%eval " );
15109 char * s_emt = FindStr( text, "[%emt " );
15111 if( s_eval != NULL || s_emt != NULL ) {
15115 if( s_eval != NULL ) {
15116 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15120 if( delim != ']' ) {
15125 if( s_emt != NULL ) {
15130 /* We expect something like: [+|-]nnn.nn/dd */
15133 if(*text != '{') return text; // [HGM] braces: must be normal comment
15135 sep = strchr( text, '/' );
15136 if( sep == NULL || sep < (text+4) ) {
15141 if(p[1] == '(') { // comment starts with PV
15142 p = strchr(p, ')'); // locate end of PV
15143 if(p == NULL || sep < p+5) return text;
15144 // at this point we have something like "{(.*) +0.23/6 ..."
15145 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15146 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15147 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15149 time = -1; sec = -1; deci = -1;
15150 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15151 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15152 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15153 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15157 if( score_lo < 0 || score_lo >= 100 ) {
15161 if(sec >= 0) time = 600*time + 10*sec; else
15162 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15164 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15166 /* [HGM] PV time: now locate end of PV info */
15167 while( *++sep >= '0' && *sep <= '9'); // strip depth
15169 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15171 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15173 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15174 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15185 pvInfoList[index-1].depth = depth;
15186 pvInfoList[index-1].score = score;
15187 pvInfoList[index-1].time = 10*time; // centi-sec
15188 if(*sep == '}') *sep = 0; else *--sep = '{';
15189 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15195 SendToProgram (char *message, ChessProgramState *cps)
15197 int count, outCount, error;
15200 if (cps->pr == NoProc) return;
15203 if (appData.debugMode) {
15206 fprintf(debugFP, "%ld >%-6s: %s",
15207 SubtractTimeMarks(&now, &programStartTime),
15208 cps->which, message);
15210 fprintf(serverFP, "%ld >%-6s: %s",
15211 SubtractTimeMarks(&now, &programStartTime),
15212 cps->which, message), fflush(serverFP);
15215 count = strlen(message);
15216 outCount = OutputToProcess(cps->pr, message, count, &error);
15217 if (outCount < count && !exiting
15218 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15219 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15220 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15221 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15222 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15223 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15224 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15225 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15227 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15228 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15229 gameInfo.result = res;
15231 gameInfo.resultDetails = StrSave(buf);
15233 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15234 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15239 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15243 ChessProgramState *cps = (ChessProgramState *)closure;
15245 if (isr != cps->isr) return; /* Killed intentionally */
15248 RemoveInputSource(cps->isr);
15249 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15250 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15251 _(cps->which), cps->program);
15252 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15253 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15254 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15255 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15256 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15258 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15259 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15260 gameInfo.result = res;
15262 gameInfo.resultDetails = StrSave(buf);
15264 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15265 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15267 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15268 _(cps->which), cps->program);
15269 RemoveInputSource(cps->isr);
15271 /* [AS] Program is misbehaving badly... kill it */
15272 if( count == -2 ) {
15273 DestroyChildProcess( cps->pr, 9 );
15277 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15282 if ((end_str = strchr(message, '\r')) != NULL)
15283 *end_str = NULLCHAR;
15284 if ((end_str = strchr(message, '\n')) != NULL)
15285 *end_str = NULLCHAR;
15287 if (appData.debugMode) {
15288 TimeMark now; int print = 1;
15289 char *quote = ""; char c; int i;
15291 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15292 char start = message[0];
15293 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15294 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15295 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15296 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15297 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15298 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15299 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15300 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15301 sscanf(message, "hint: %c", &c)!=1 &&
15302 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15303 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15304 print = (appData.engineComments >= 2);
15306 message[0] = start; // restore original message
15310 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15311 SubtractTimeMarks(&now, &programStartTime), cps->which,
15315 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15316 SubtractTimeMarks(&now, &programStartTime), cps->which,
15318 message), fflush(serverFP);
15322 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15323 if (appData.icsEngineAnalyze) {
15324 if (strstr(message, "whisper") != NULL ||
15325 strstr(message, "kibitz") != NULL ||
15326 strstr(message, "tellics") != NULL) return;
15329 HandleMachineMove(message, cps);
15334 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15339 if( timeControl_2 > 0 ) {
15340 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15341 tc = timeControl_2;
15344 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15345 inc /= cps->timeOdds;
15346 st /= cps->timeOdds;
15348 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15351 /* Set exact time per move, normally using st command */
15352 if (cps->stKludge) {
15353 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15355 if (seconds == 0) {
15356 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15358 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15361 snprintf(buf, MSG_SIZ, "st %d\n", st);
15364 /* Set conventional or incremental time control, using level command */
15365 if (seconds == 0) {
15366 /* Note old gnuchess bug -- minutes:seconds used to not work.
15367 Fixed in later versions, but still avoid :seconds
15368 when seconds is 0. */
15369 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15371 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15372 seconds, inc/1000.);
15375 SendToProgram(buf, cps);
15377 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15378 /* Orthogonally, limit search to given depth */
15380 if (cps->sdKludge) {
15381 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15383 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15385 SendToProgram(buf, cps);
15388 if(cps->nps >= 0) { /* [HGM] nps */
15389 if(cps->supportsNPS == FALSE)
15390 cps->nps = -1; // don't use if engine explicitly says not supported!
15392 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15393 SendToProgram(buf, cps);
15398 ChessProgramState *
15400 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15402 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15403 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15409 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15411 char message[MSG_SIZ];
15414 /* Note: this routine must be called when the clocks are stopped
15415 or when they have *just* been set or switched; otherwise
15416 it will be off by the time since the current tick started.
15418 if (machineWhite) {
15419 time = whiteTimeRemaining / 10;
15420 otime = blackTimeRemaining / 10;
15422 time = blackTimeRemaining / 10;
15423 otime = whiteTimeRemaining / 10;
15425 /* [HGM] translate opponent's time by time-odds factor */
15426 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15428 if (time <= 0) time = 1;
15429 if (otime <= 0) otime = 1;
15431 snprintf(message, MSG_SIZ, "time %ld\n", time);
15432 SendToProgram(message, cps);
15434 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15435 SendToProgram(message, cps);
15439 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15442 int len = strlen(name);
15445 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15447 sscanf(*p, "%d", &val);
15449 while (**p && **p != ' ')
15451 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15452 SendToProgram(buf, cps);
15459 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15462 int len = strlen(name);
15463 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15465 sscanf(*p, "%d", loc);
15466 while (**p && **p != ' ') (*p)++;
15467 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15468 SendToProgram(buf, cps);
15475 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15478 int len = strlen(name);
15479 if (strncmp((*p), name, len) == 0
15480 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15482 sscanf(*p, "%[^\"]", loc);
15483 while (**p && **p != '\"') (*p)++;
15484 if (**p == '\"') (*p)++;
15485 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15486 SendToProgram(buf, cps);
15493 ParseOption (Option *opt, ChessProgramState *cps)
15494 // [HGM] options: process the string that defines an engine option, and determine
15495 // name, type, default value, and allowed value range
15497 char *p, *q, buf[MSG_SIZ];
15498 int n, min = (-1)<<31, max = 1<<31, def;
15500 if(p = strstr(opt->name, " -spin ")) {
15501 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15502 if(max < min) max = min; // enforce consistency
15503 if(def < min) def = min;
15504 if(def > max) def = max;
15509 } else if((p = strstr(opt->name, " -slider "))) {
15510 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15511 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15512 if(max < min) max = min; // enforce consistency
15513 if(def < min) def = min;
15514 if(def > max) def = max;
15518 opt->type = Spin; // Slider;
15519 } else if((p = strstr(opt->name, " -string "))) {
15520 opt->textValue = p+9;
15521 opt->type = TextBox;
15522 } else if((p = strstr(opt->name, " -file "))) {
15523 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15524 opt->textValue = p+7;
15525 opt->type = FileName; // FileName;
15526 } else if((p = strstr(opt->name, " -path "))) {
15527 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15528 opt->textValue = p+7;
15529 opt->type = PathName; // PathName;
15530 } else if(p = strstr(opt->name, " -check ")) {
15531 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15532 opt->value = (def != 0);
15533 opt->type = CheckBox;
15534 } else if(p = strstr(opt->name, " -combo ")) {
15535 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15536 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15537 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15538 opt->value = n = 0;
15539 while(q = StrStr(q, " /// ")) {
15540 n++; *q = 0; // count choices, and null-terminate each of them
15542 if(*q == '*') { // remember default, which is marked with * prefix
15546 cps->comboList[cps->comboCnt++] = q;
15548 cps->comboList[cps->comboCnt++] = NULL;
15550 opt->type = ComboBox;
15551 } else if(p = strstr(opt->name, " -button")) {
15552 opt->type = Button;
15553 } else if(p = strstr(opt->name, " -save")) {
15554 opt->type = SaveButton;
15555 } else return FALSE;
15556 *p = 0; // terminate option name
15557 // now look if the command-line options define a setting for this engine option.
15558 if(cps->optionSettings && cps->optionSettings[0])
15559 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15560 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15561 snprintf(buf, MSG_SIZ, "option %s", p);
15562 if(p = strstr(buf, ",")) *p = 0;
15563 if(q = strchr(buf, '=')) switch(opt->type) {
15565 for(n=0; n<opt->max; n++)
15566 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15569 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15573 opt->value = atoi(q+1);
15578 SendToProgram(buf, cps);
15584 FeatureDone (ChessProgramState *cps, int val)
15586 DelayedEventCallback cb = GetDelayedEvent();
15587 if ((cb == InitBackEnd3 && cps == &first) ||
15588 (cb == SettingsMenuIfReady && cps == &second) ||
15589 (cb == LoadEngine) ||
15590 (cb == TwoMachinesEventIfReady)) {
15591 CancelDelayedEvent();
15592 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15594 cps->initDone = val;
15597 /* Parse feature command from engine */
15599 ParseFeatures (char *args, ChessProgramState *cps)
15607 while (*p == ' ') p++;
15608 if (*p == NULLCHAR) return;
15610 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15611 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15612 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15613 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15614 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15615 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15616 if (BoolFeature(&p, "reuse", &val, cps)) {
15617 /* Engine can disable reuse, but can't enable it if user said no */
15618 if (!val) cps->reuse = FALSE;
15621 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15622 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15623 if (gameMode == TwoMachinesPlay) {
15624 DisplayTwoMachinesTitle();
15630 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15631 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15632 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15633 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15634 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15635 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15636 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15637 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15638 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15639 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15640 if (IntFeature(&p, "done", &val, cps)) {
15641 FeatureDone(cps, val);
15644 /* Added by Tord: */
15645 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15646 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15647 /* End of additions by Tord */
15649 /* [HGM] added features: */
15650 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15651 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15652 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15653 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15654 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15655 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15656 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15657 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15658 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15659 SendToProgram(buf, cps);
15662 if(cps->nrOptions >= MAX_OPTIONS) {
15664 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15665 DisplayError(buf, 0);
15669 /* End of additions by HGM */
15671 /* unknown feature: complain and skip */
15673 while (*q && *q != '=') q++;
15674 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15675 SendToProgram(buf, cps);
15681 while (*p && *p != '\"') p++;
15682 if (*p == '\"') p++;
15684 while (*p && *p != ' ') p++;
15692 PeriodicUpdatesEvent (int newState)
15694 if (newState == appData.periodicUpdates)
15697 appData.periodicUpdates=newState;
15699 /* Display type changes, so update it now */
15700 // DisplayAnalysis();
15702 /* Get the ball rolling again... */
15704 AnalysisPeriodicEvent(1);
15705 StartAnalysisClock();
15710 PonderNextMoveEvent (int newState)
15712 if (newState == appData.ponderNextMove) return;
15713 if (gameMode == EditPosition) EditPositionDone(TRUE);
15715 SendToProgram("hard\n", &first);
15716 if (gameMode == TwoMachinesPlay) {
15717 SendToProgram("hard\n", &second);
15720 SendToProgram("easy\n", &first);
15721 thinkOutput[0] = NULLCHAR;
15722 if (gameMode == TwoMachinesPlay) {
15723 SendToProgram("easy\n", &second);
15726 appData.ponderNextMove = newState;
15730 NewSettingEvent (int option, int *feature, char *command, int value)
15734 if (gameMode == EditPosition) EditPositionDone(TRUE);
15735 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15736 if(feature == NULL || *feature) SendToProgram(buf, &first);
15737 if (gameMode == TwoMachinesPlay) {
15738 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15743 ShowThinkingEvent ()
15744 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15746 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15747 int newState = appData.showThinking
15748 // [HGM] thinking: other features now need thinking output as well
15749 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15751 if (oldState == newState) return;
15752 oldState = newState;
15753 if (gameMode == EditPosition) EditPositionDone(TRUE);
15755 SendToProgram("post\n", &first);
15756 if (gameMode == TwoMachinesPlay) {
15757 SendToProgram("post\n", &second);
15760 SendToProgram("nopost\n", &first);
15761 thinkOutput[0] = NULLCHAR;
15762 if (gameMode == TwoMachinesPlay) {
15763 SendToProgram("nopost\n", &second);
15766 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15770 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15772 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15773 if (pr == NoProc) return;
15774 AskQuestion(title, question, replyPrefix, pr);
15778 TypeInEvent (char firstChar)
15780 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15781 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15782 gameMode == AnalyzeMode || gameMode == EditGame ||
15783 gameMode == EditPosition || gameMode == IcsExamining ||
15784 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15785 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15786 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15787 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15788 gameMode == Training) PopUpMoveDialog(firstChar);
15792 TypeInDoneEvent (char *move)
15795 int n, fromX, fromY, toX, toY;
15797 ChessMove moveType;
15800 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15801 EditPositionPasteFEN(move);
15804 // [HGM] movenum: allow move number to be typed in any mode
15805 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15809 // undocumented kludge: allow command-line option to be typed in!
15810 // (potentially fatal, and does not implement the effect of the option.)
15811 // should only be used for options that are values on which future decisions will be made,
15812 // and definitely not on options that would be used during initialization.
15813 if(strstr(move, "!!! -") == move) {
15814 ParseArgsFromString(move+4);
15818 if (gameMode != EditGame && currentMove != forwardMostMove &&
15819 gameMode != Training) {
15820 DisplayMoveError(_("Displayed move is not current"));
15822 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15823 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15824 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15825 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15826 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15827 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15829 DisplayMoveError(_("Could not parse move"));
15835 DisplayMove (int moveNumber)
15837 char message[MSG_SIZ];
15839 char cpThinkOutput[MSG_SIZ];
15841 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15843 if (moveNumber == forwardMostMove - 1 ||
15844 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15846 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15848 if (strchr(cpThinkOutput, '\n')) {
15849 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15852 *cpThinkOutput = NULLCHAR;
15855 /* [AS] Hide thinking from human user */
15856 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15857 *cpThinkOutput = NULLCHAR;
15858 if( thinkOutput[0] != NULLCHAR ) {
15861 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15862 cpThinkOutput[i] = '.';
15864 cpThinkOutput[i] = NULLCHAR;
15865 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15869 if (moveNumber == forwardMostMove - 1 &&
15870 gameInfo.resultDetails != NULL) {
15871 if (gameInfo.resultDetails[0] == NULLCHAR) {
15872 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15874 snprintf(res, MSG_SIZ, " {%s} %s",
15875 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15881 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15882 DisplayMessage(res, cpThinkOutput);
15884 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15885 WhiteOnMove(moveNumber) ? " " : ".. ",
15886 parseList[moveNumber], res);
15887 DisplayMessage(message, cpThinkOutput);
15892 DisplayComment (int moveNumber, char *text)
15894 char title[MSG_SIZ];
15896 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15897 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15899 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15900 WhiteOnMove(moveNumber) ? " " : ".. ",
15901 parseList[moveNumber]);
15903 if (text != NULL && (appData.autoDisplayComment || commentUp))
15904 CommentPopUp(title, text);
15907 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15908 * might be busy thinking or pondering. It can be omitted if your
15909 * gnuchess is configured to stop thinking immediately on any user
15910 * input. However, that gnuchess feature depends on the FIONREAD
15911 * ioctl, which does not work properly on some flavors of Unix.
15914 Attention (ChessProgramState *cps)
15917 if (!cps->useSigint) return;
15918 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15919 switch (gameMode) {
15920 case MachinePlaysWhite:
15921 case MachinePlaysBlack:
15922 case TwoMachinesPlay:
15923 case IcsPlayingWhite:
15924 case IcsPlayingBlack:
15927 /* Skip if we know it isn't thinking */
15928 if (!cps->maybeThinking) return;
15929 if (appData.debugMode)
15930 fprintf(debugFP, "Interrupting %s\n", cps->which);
15931 InterruptChildProcess(cps->pr);
15932 cps->maybeThinking = FALSE;
15937 #endif /*ATTENTION*/
15943 if (whiteTimeRemaining <= 0) {
15946 if (appData.icsActive) {
15947 if (appData.autoCallFlag &&
15948 gameMode == IcsPlayingBlack && !blackFlag) {
15949 SendToICS(ics_prefix);
15950 SendToICS("flag\n");
15954 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15956 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15957 if (appData.autoCallFlag) {
15958 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15965 if (blackTimeRemaining <= 0) {
15968 if (appData.icsActive) {
15969 if (appData.autoCallFlag &&
15970 gameMode == IcsPlayingWhite && !whiteFlag) {
15971 SendToICS(ics_prefix);
15972 SendToICS("flag\n");
15976 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15978 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15979 if (appData.autoCallFlag) {
15980 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15991 CheckTimeControl ()
15993 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15994 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15997 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15999 if ( !WhiteOnMove(forwardMostMove) ) {
16000 /* White made time control */
16001 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16002 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16003 /* [HGM] time odds: correct new time quota for time odds! */
16004 / WhitePlayer()->timeOdds;
16005 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16007 lastBlack -= blackTimeRemaining;
16008 /* Black made time control */
16009 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16010 / WhitePlayer()->other->timeOdds;
16011 lastWhite = whiteTimeRemaining;
16016 DisplayBothClocks ()
16018 int wom = gameMode == EditPosition ?
16019 !blackPlaysFirst : WhiteOnMove(currentMove);
16020 DisplayWhiteClock(whiteTimeRemaining, wom);
16021 DisplayBlackClock(blackTimeRemaining, !wom);
16025 /* Timekeeping seems to be a portability nightmare. I think everyone
16026 has ftime(), but I'm really not sure, so I'm including some ifdefs
16027 to use other calls if you don't. Clocks will be less accurate if
16028 you have neither ftime nor gettimeofday.
16031 /* VS 2008 requires the #include outside of the function */
16032 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16033 #include <sys/timeb.h>
16036 /* Get the current time as a TimeMark */
16038 GetTimeMark (TimeMark *tm)
16040 #if HAVE_GETTIMEOFDAY
16042 struct timeval timeVal;
16043 struct timezone timeZone;
16045 gettimeofday(&timeVal, &timeZone);
16046 tm->sec = (long) timeVal.tv_sec;
16047 tm->ms = (int) (timeVal.tv_usec / 1000L);
16049 #else /*!HAVE_GETTIMEOFDAY*/
16052 // include <sys/timeb.h> / moved to just above start of function
16053 struct timeb timeB;
16056 tm->sec = (long) timeB.time;
16057 tm->ms = (int) timeB.millitm;
16059 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16060 tm->sec = (long) time(NULL);
16066 /* Return the difference in milliseconds between two
16067 time marks. We assume the difference will fit in a long!
16070 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16072 return 1000L*(tm2->sec - tm1->sec) +
16073 (long) (tm2->ms - tm1->ms);
16078 * Code to manage the game clocks.
16080 * In tournament play, black starts the clock and then white makes a move.
16081 * We give the human user a slight advantage if he is playing white---the
16082 * clocks don't run until he makes his first move, so it takes zero time.
16083 * Also, we don't account for network lag, so we could get out of sync
16084 * with GNU Chess's clock -- but then, referees are always right.
16087 static TimeMark tickStartTM;
16088 static long intendedTickLength;
16091 NextTickLength (long timeRemaining)
16093 long nominalTickLength, nextTickLength;
16095 if (timeRemaining > 0L && timeRemaining <= 10000L)
16096 nominalTickLength = 100L;
16098 nominalTickLength = 1000L;
16099 nextTickLength = timeRemaining % nominalTickLength;
16100 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16102 return nextTickLength;
16105 /* Adjust clock one minute up or down */
16107 AdjustClock (Boolean which, int dir)
16109 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16110 if(which) blackTimeRemaining += 60000*dir;
16111 else whiteTimeRemaining += 60000*dir;
16112 DisplayBothClocks();
16113 adjustedClock = TRUE;
16116 /* Stop clocks and reset to a fresh time control */
16120 (void) StopClockTimer();
16121 if (appData.icsActive) {
16122 whiteTimeRemaining = blackTimeRemaining = 0;
16123 } else if (searchTime) {
16124 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16125 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16126 } else { /* [HGM] correct new time quote for time odds */
16127 whiteTC = blackTC = fullTimeControlString;
16128 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16129 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16131 if (whiteFlag || blackFlag) {
16133 whiteFlag = blackFlag = FALSE;
16135 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16136 DisplayBothClocks();
16137 adjustedClock = FALSE;
16140 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16142 /* Decrement running clock by amount of time that has passed */
16146 long timeRemaining;
16147 long lastTickLength, fudge;
16150 if (!appData.clockMode) return;
16151 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16155 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16157 /* Fudge if we woke up a little too soon */
16158 fudge = intendedTickLength - lastTickLength;
16159 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16161 if (WhiteOnMove(forwardMostMove)) {
16162 if(whiteNPS >= 0) lastTickLength = 0;
16163 timeRemaining = whiteTimeRemaining -= lastTickLength;
16164 if(timeRemaining < 0 && !appData.icsActive) {
16165 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16166 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16167 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16168 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16171 DisplayWhiteClock(whiteTimeRemaining - fudge,
16172 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16174 if(blackNPS >= 0) lastTickLength = 0;
16175 timeRemaining = blackTimeRemaining -= lastTickLength;
16176 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16177 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16179 blackStartMove = forwardMostMove;
16180 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16183 DisplayBlackClock(blackTimeRemaining - fudge,
16184 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16186 if (CheckFlags()) return;
16189 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16190 StartClockTimer(intendedTickLength);
16192 /* if the time remaining has fallen below the alarm threshold, sound the
16193 * alarm. if the alarm has sounded and (due to a takeback or time control
16194 * with increment) the time remaining has increased to a level above the
16195 * threshold, reset the alarm so it can sound again.
16198 if (appData.icsActive && appData.icsAlarm) {
16200 /* make sure we are dealing with the user's clock */
16201 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16202 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16205 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16206 alarmSounded = FALSE;
16207 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16209 alarmSounded = TRUE;
16215 /* A player has just moved, so stop the previously running
16216 clock and (if in clock mode) start the other one.
16217 We redisplay both clocks in case we're in ICS mode, because
16218 ICS gives us an update to both clocks after every move.
16219 Note that this routine is called *after* forwardMostMove
16220 is updated, so the last fractional tick must be subtracted
16221 from the color that is *not* on move now.
16224 SwitchClocks (int newMoveNr)
16226 long lastTickLength;
16228 int flagged = FALSE;
16232 if (StopClockTimer() && appData.clockMode) {
16233 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16234 if (!WhiteOnMove(forwardMostMove)) {
16235 if(blackNPS >= 0) lastTickLength = 0;
16236 blackTimeRemaining -= lastTickLength;
16237 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16238 // if(pvInfoList[forwardMostMove].time == -1)
16239 pvInfoList[forwardMostMove].time = // use GUI time
16240 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16242 if(whiteNPS >= 0) lastTickLength = 0;
16243 whiteTimeRemaining -= lastTickLength;
16244 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16245 // if(pvInfoList[forwardMostMove].time == -1)
16246 pvInfoList[forwardMostMove].time =
16247 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16249 flagged = CheckFlags();
16251 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16252 CheckTimeControl();
16254 if (flagged || !appData.clockMode) return;
16256 switch (gameMode) {
16257 case MachinePlaysBlack:
16258 case MachinePlaysWhite:
16259 case BeginningOfGame:
16260 if (pausing) return;
16264 case PlayFromGameFile:
16272 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16273 if(WhiteOnMove(forwardMostMove))
16274 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16275 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16279 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16280 whiteTimeRemaining : blackTimeRemaining);
16281 StartClockTimer(intendedTickLength);
16285 /* Stop both clocks */
16289 long lastTickLength;
16292 if (!StopClockTimer()) return;
16293 if (!appData.clockMode) return;
16297 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16298 if (WhiteOnMove(forwardMostMove)) {
16299 if(whiteNPS >= 0) lastTickLength = 0;
16300 whiteTimeRemaining -= lastTickLength;
16301 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16303 if(blackNPS >= 0) lastTickLength = 0;
16304 blackTimeRemaining -= lastTickLength;
16305 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16310 /* Start clock of player on move. Time may have been reset, so
16311 if clock is already running, stop and restart it. */
16315 (void) StopClockTimer(); /* in case it was running already */
16316 DisplayBothClocks();
16317 if (CheckFlags()) return;
16319 if (!appData.clockMode) return;
16320 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16322 GetTimeMark(&tickStartTM);
16323 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16324 whiteTimeRemaining : blackTimeRemaining);
16326 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16327 whiteNPS = blackNPS = -1;
16328 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16329 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16330 whiteNPS = first.nps;
16331 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16332 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16333 blackNPS = first.nps;
16334 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16335 whiteNPS = second.nps;
16336 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16337 blackNPS = second.nps;
16338 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16340 StartClockTimer(intendedTickLength);
16344 TimeString (long ms)
16346 long second, minute, hour, day;
16348 static char buf[32];
16350 if (ms > 0 && ms <= 9900) {
16351 /* convert milliseconds to tenths, rounding up */
16352 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16354 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16358 /* convert milliseconds to seconds, rounding up */
16359 /* use floating point to avoid strangeness of integer division
16360 with negative dividends on many machines */
16361 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16368 day = second / (60 * 60 * 24);
16369 second = second % (60 * 60 * 24);
16370 hour = second / (60 * 60);
16371 second = second % (60 * 60);
16372 minute = second / 60;
16373 second = second % 60;
16376 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16377 sign, day, hour, minute, second);
16379 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16381 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16388 * This is necessary because some C libraries aren't ANSI C compliant yet.
16391 StrStr (char *string, char *match)
16395 length = strlen(match);
16397 for (i = strlen(string) - length; i >= 0; i--, string++)
16398 if (!strncmp(match, string, length))
16405 StrCaseStr (char *string, char *match)
16409 length = strlen(match);
16411 for (i = strlen(string) - length; i >= 0; i--, string++) {
16412 for (j = 0; j < length; j++) {
16413 if (ToLower(match[j]) != ToLower(string[j]))
16416 if (j == length) return string;
16424 StrCaseCmp (char *s1, char *s2)
16429 c1 = ToLower(*s1++);
16430 c2 = ToLower(*s2++);
16431 if (c1 > c2) return 1;
16432 if (c1 < c2) return -1;
16433 if (c1 == NULLCHAR) return 0;
16441 return isupper(c) ? tolower(c) : c;
16448 return islower(c) ? toupper(c) : c;
16450 #endif /* !_amigados */
16457 if ((ret = (char *) malloc(strlen(s) + 1)))
16459 safeStrCpy(ret, s, strlen(s)+1);
16465 StrSavePtr (char *s, char **savePtr)
16470 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16471 safeStrCpy(*savePtr, s, strlen(s)+1);
16483 clock = time((time_t *)NULL);
16484 tm = localtime(&clock);
16485 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16486 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16487 return StrSave(buf);
16492 PositionToFEN (int move, char *overrideCastling)
16494 int i, j, fromX, fromY, toX, toY;
16501 whiteToPlay = (gameMode == EditPosition) ?
16502 !blackPlaysFirst : (move % 2 == 0);
16505 /* Piece placement data */
16506 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16507 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16509 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16510 if (boards[move][i][j] == EmptySquare) {
16512 } else { ChessSquare piece = boards[move][i][j];
16513 if (emptycount > 0) {
16514 if(emptycount<10) /* [HGM] can be >= 10 */
16515 *p++ = '0' + emptycount;
16516 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16519 if(PieceToChar(piece) == '+') {
16520 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16522 piece = (ChessSquare)(DEMOTED piece);
16524 *p++ = PieceToChar(piece);
16526 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16527 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16532 if (emptycount > 0) {
16533 if(emptycount<10) /* [HGM] can be >= 10 */
16534 *p++ = '0' + emptycount;
16535 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16542 /* [HGM] print Crazyhouse or Shogi holdings */
16543 if( gameInfo.holdingsWidth ) {
16544 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16546 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16547 piece = boards[move][i][BOARD_WIDTH-1];
16548 if( piece != EmptySquare )
16549 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16550 *p++ = PieceToChar(piece);
16552 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16553 piece = boards[move][BOARD_HEIGHT-i-1][0];
16554 if( piece != EmptySquare )
16555 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16556 *p++ = PieceToChar(piece);
16559 if( q == p ) *p++ = '-';
16565 *p++ = whiteToPlay ? 'w' : 'b';
16568 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16569 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16571 if(nrCastlingRights) {
16573 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16574 /* [HGM] write directly from rights */
16575 if(boards[move][CASTLING][2] != NoRights &&
16576 boards[move][CASTLING][0] != NoRights )
16577 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16578 if(boards[move][CASTLING][2] != NoRights &&
16579 boards[move][CASTLING][1] != NoRights )
16580 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16581 if(boards[move][CASTLING][5] != NoRights &&
16582 boards[move][CASTLING][3] != NoRights )
16583 *p++ = boards[move][CASTLING][3] + AAA;
16584 if(boards[move][CASTLING][5] != NoRights &&
16585 boards[move][CASTLING][4] != NoRights )
16586 *p++ = boards[move][CASTLING][4] + AAA;
16589 /* [HGM] write true castling rights */
16590 if( nrCastlingRights == 6 ) {
16591 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16592 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16593 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16594 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16595 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16596 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16597 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16598 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16601 if (q == p) *p++ = '-'; /* No castling rights */
16605 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16606 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16607 /* En passant target square */
16608 if (move > backwardMostMove) {
16609 fromX = moveList[move - 1][0] - AAA;
16610 fromY = moveList[move - 1][1] - ONE;
16611 toX = moveList[move - 1][2] - AAA;
16612 toY = moveList[move - 1][3] - ONE;
16613 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16614 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16615 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16617 /* 2-square pawn move just happened */
16619 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16623 } else if(move == backwardMostMove) {
16624 // [HGM] perhaps we should always do it like this, and forget the above?
16625 if((signed char)boards[move][EP_STATUS] >= 0) {
16626 *p++ = boards[move][EP_STATUS] + AAA;
16627 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16638 /* [HGM] find reversible plies */
16639 { int i = 0, j=move;
16641 if (appData.debugMode) { int k;
16642 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16643 for(k=backwardMostMove; k<=forwardMostMove; k++)
16644 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16648 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16649 if( j == backwardMostMove ) i += initialRulePlies;
16650 sprintf(p, "%d ", i);
16651 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16653 /* Fullmove number */
16654 sprintf(p, "%d", (move / 2) + 1);
16656 return StrSave(buf);
16660 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16669 /* [HGM] by default clear Crazyhouse holdings, if present */
16670 if(gameInfo.holdingsWidth) {
16671 for(i=0; i<BOARD_HEIGHT; i++) {
16672 board[i][0] = EmptySquare; /* black holdings */
16673 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16674 board[i][1] = (ChessSquare) 0; /* black counts */
16675 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16679 /* Piece placement data */
16680 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16683 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16684 if (*p == '/') p++;
16685 emptycount = gameInfo.boardWidth - j;
16686 while (emptycount--)
16687 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16689 #if(BOARD_FILES >= 10)
16690 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16691 p++; emptycount=10;
16692 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16693 while (emptycount--)
16694 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16696 } else if (isdigit(*p)) {
16697 emptycount = *p++ - '0';
16698 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16699 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16700 while (emptycount--)
16701 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16702 } else if (*p == '+' || isalpha(*p)) {
16703 if (j >= gameInfo.boardWidth) return FALSE;
16705 piece = CharToPiece(*++p);
16706 if(piece == EmptySquare) return FALSE; /* unknown piece */
16707 piece = (ChessSquare) (PROMOTED piece ); p++;
16708 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16709 } else piece = CharToPiece(*p++);
16711 if(piece==EmptySquare) return FALSE; /* unknown piece */
16712 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16713 piece = (ChessSquare) (PROMOTED piece);
16714 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16717 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16723 while (*p == '/' || *p == ' ') p++;
16725 /* [HGM] look for Crazyhouse holdings here */
16726 while(*p==' ') p++;
16727 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16729 if(*p == '-' ) p++; /* empty holdings */ else {
16730 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16731 /* if we would allow FEN reading to set board size, we would */
16732 /* have to add holdings and shift the board read so far here */
16733 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16735 if((int) piece >= (int) BlackPawn ) {
16736 i = (int)piece - (int)BlackPawn;
16737 i = PieceToNumber((ChessSquare)i);
16738 if( i >= gameInfo.holdingsSize ) return FALSE;
16739 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16740 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16742 i = (int)piece - (int)WhitePawn;
16743 i = PieceToNumber((ChessSquare)i);
16744 if( i >= gameInfo.holdingsSize ) return FALSE;
16745 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16746 board[i][BOARD_WIDTH-2]++; /* black holdings */
16753 while(*p == ' ') p++;
16757 if(appData.colorNickNames) {
16758 if( c == appData.colorNickNames[0] ) c = 'w'; else
16759 if( c == appData.colorNickNames[1] ) c = 'b';
16763 *blackPlaysFirst = FALSE;
16766 *blackPlaysFirst = TRUE;
16772 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16773 /* return the extra info in global variiables */
16775 /* set defaults in case FEN is incomplete */
16776 board[EP_STATUS] = EP_UNKNOWN;
16777 for(i=0; i<nrCastlingRights; i++ ) {
16778 board[CASTLING][i] =
16779 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16780 } /* assume possible unless obviously impossible */
16781 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16782 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16783 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16784 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16785 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16786 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16787 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16788 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16791 while(*p==' ') p++;
16792 if(nrCastlingRights) {
16793 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16794 /* castling indicator present, so default becomes no castlings */
16795 for(i=0; i<nrCastlingRights; i++ ) {
16796 board[CASTLING][i] = NoRights;
16799 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16800 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16801 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16802 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16803 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16805 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16806 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16807 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16809 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16810 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16811 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16812 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16813 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16814 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16817 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16818 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16819 board[CASTLING][2] = whiteKingFile;
16822 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16823 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16824 board[CASTLING][2] = whiteKingFile;
16827 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16828 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16829 board[CASTLING][5] = blackKingFile;
16832 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16833 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16834 board[CASTLING][5] = blackKingFile;
16837 default: /* FRC castlings */
16838 if(c >= 'a') { /* black rights */
16839 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16840 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16841 if(i == BOARD_RGHT) break;
16842 board[CASTLING][5] = i;
16844 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16845 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16847 board[CASTLING][3] = c;
16849 board[CASTLING][4] = c;
16850 } else { /* white rights */
16851 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16852 if(board[0][i] == WhiteKing) break;
16853 if(i == BOARD_RGHT) break;
16854 board[CASTLING][2] = i;
16855 c -= AAA - 'a' + 'A';
16856 if(board[0][c] >= WhiteKing) break;
16858 board[CASTLING][0] = c;
16860 board[CASTLING][1] = c;
16864 for(i=0; i<nrCastlingRights; i++)
16865 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16866 if (appData.debugMode) {
16867 fprintf(debugFP, "FEN castling rights:");
16868 for(i=0; i<nrCastlingRights; i++)
16869 fprintf(debugFP, " %d", board[CASTLING][i]);
16870 fprintf(debugFP, "\n");
16873 while(*p==' ') p++;
16876 /* read e.p. field in games that know e.p. capture */
16877 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16878 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16880 p++; board[EP_STATUS] = EP_NONE;
16882 char c = *p++ - AAA;
16884 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16885 if(*p >= '0' && *p <='9') p++;
16886 board[EP_STATUS] = c;
16891 if(sscanf(p, "%d", &i) == 1) {
16892 FENrulePlies = i; /* 50-move ply counter */
16893 /* (The move number is still ignored) */
16900 EditPositionPasteFEN (char *fen)
16903 Board initial_position;
16905 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16906 DisplayError(_("Bad FEN position in clipboard"), 0);
16909 int savedBlackPlaysFirst = blackPlaysFirst;
16910 EditPositionEvent();
16911 blackPlaysFirst = savedBlackPlaysFirst;
16912 CopyBoard(boards[0], initial_position);
16913 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16914 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16915 DisplayBothClocks();
16916 DrawPosition(FALSE, boards[currentMove]);
16921 static char cseq[12] = "\\ ";
16924 set_cont_sequence (char *new_seq)
16929 // handle bad attempts to set the sequence
16931 return 0; // acceptable error - no debug
16933 len = strlen(new_seq);
16934 ret = (len > 0) && (len < sizeof(cseq));
16936 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16937 else if (appData.debugMode)
16938 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16943 reformat a source message so words don't cross the width boundary. internal
16944 newlines are not removed. returns the wrapped size (no null character unless
16945 included in source message). If dest is NULL, only calculate the size required
16946 for the dest buffer. lp argument indicats line position upon entry, and it's
16947 passed back upon exit.
16950 wrap (char *dest, char *src, int count, int width, int *lp)
16952 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16954 cseq_len = strlen(cseq);
16955 old_line = line = *lp;
16956 ansi = len = clen = 0;
16958 for (i=0; i < count; i++)
16960 if (src[i] == '\033')
16963 // if we hit the width, back up
16964 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16966 // store i & len in case the word is too long
16967 old_i = i, old_len = len;
16969 // find the end of the last word
16970 while (i && src[i] != ' ' && src[i] != '\n')
16976 // word too long? restore i & len before splitting it
16977 if ((old_i-i+clen) >= width)
16984 if (i && src[i-1] == ' ')
16987 if (src[i] != ' ' && src[i] != '\n')
16994 // now append the newline and continuation sequence
16999 strncpy(dest+len, cseq, cseq_len);
17007 dest[len] = src[i];
17011 if (src[i] == '\n')
17016 if (dest && appData.debugMode)
17018 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17019 count, width, line, len, *lp);
17020 show_bytes(debugFP, src, count);
17021 fprintf(debugFP, "\ndest: ");
17022 show_bytes(debugFP, dest, len);
17023 fprintf(debugFP, "\n");
17025 *lp = dest ? line : old_line;
17030 // [HGM] vari: routines for shelving variations
17031 Boolean modeRestore = FALSE;
17034 PushInner (int firstMove, int lastMove)
17036 int i, j, nrMoves = lastMove - firstMove;
17038 // push current tail of game on stack
17039 savedResult[storedGames] = gameInfo.result;
17040 savedDetails[storedGames] = gameInfo.resultDetails;
17041 gameInfo.resultDetails = NULL;
17042 savedFirst[storedGames] = firstMove;
17043 savedLast [storedGames] = lastMove;
17044 savedFramePtr[storedGames] = framePtr;
17045 framePtr -= nrMoves; // reserve space for the boards
17046 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17047 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17048 for(j=0; j<MOVE_LEN; j++)
17049 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17050 for(j=0; j<2*MOVE_LEN; j++)
17051 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17052 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17053 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17054 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17055 pvInfoList[firstMove+i-1].depth = 0;
17056 commentList[framePtr+i] = commentList[firstMove+i];
17057 commentList[firstMove+i] = NULL;
17061 forwardMostMove = firstMove; // truncate game so we can start variation
17065 PushTail (int firstMove, int lastMove)
17067 if(appData.icsActive) { // only in local mode
17068 forwardMostMove = currentMove; // mimic old ICS behavior
17071 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17073 PushInner(firstMove, lastMove);
17074 if(storedGames == 1) GreyRevert(FALSE);
17075 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17079 PopInner (Boolean annotate)
17082 char buf[8000], moveBuf[20];
17084 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17085 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17086 nrMoves = savedLast[storedGames] - currentMove;
17089 if(!WhiteOnMove(currentMove))
17090 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17091 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17092 for(i=currentMove; i<forwardMostMove; i++) {
17094 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17095 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17096 strcat(buf, moveBuf);
17097 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17098 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17102 for(i=1; i<=nrMoves; i++) { // copy last variation back
17103 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17104 for(j=0; j<MOVE_LEN; j++)
17105 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17106 for(j=0; j<2*MOVE_LEN; j++)
17107 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17108 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17109 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17110 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17111 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17112 commentList[currentMove+i] = commentList[framePtr+i];
17113 commentList[framePtr+i] = NULL;
17115 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17116 framePtr = savedFramePtr[storedGames];
17117 gameInfo.result = savedResult[storedGames];
17118 if(gameInfo.resultDetails != NULL) {
17119 free(gameInfo.resultDetails);
17121 gameInfo.resultDetails = savedDetails[storedGames];
17122 forwardMostMove = currentMove + nrMoves;
17126 PopTail (Boolean annotate)
17128 if(appData.icsActive) return FALSE; // only in local mode
17129 if(!storedGames) return FALSE; // sanity
17130 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17132 PopInner(annotate);
17133 if(currentMove < forwardMostMove) ForwardEvent(); else
17134 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17136 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17142 { // remove all shelved variations
17144 for(i=0; i<storedGames; i++) {
17145 if(savedDetails[i])
17146 free(savedDetails[i]);
17147 savedDetails[i] = NULL;
17149 for(i=framePtr; i<MAX_MOVES; i++) {
17150 if(commentList[i]) free(commentList[i]);
17151 commentList[i] = NULL;
17153 framePtr = MAX_MOVES-1;
17158 LoadVariation (int index, char *text)
17159 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17160 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17161 int level = 0, move;
17163 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17164 // first find outermost bracketing variation
17165 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17166 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17167 if(*p == '{') wait = '}'; else
17168 if(*p == '[') wait = ']'; else
17169 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17170 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17172 if(*p == wait) wait = NULLCHAR; // closing ]} found
17175 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17176 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17177 end[1] = NULLCHAR; // clip off comment beyond variation
17178 ToNrEvent(currentMove-1);
17179 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17180 // kludge: use ParsePV() to append variation to game
17181 move = currentMove;
17182 ParsePV(start, TRUE, TRUE);
17183 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17184 ClearPremoveHighlights();
17186 ToNrEvent(currentMove+1);