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(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second, pairing;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
454 int have_sent_ICS_logon = 0;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
506 ChessSquare FIDEArray[2][BOARD_FILES] = {
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510 BlackKing, BlackBishop, BlackKnight, BlackRook }
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackKing, BlackKnight, BlackRook }
520 ChessSquare KnightmateArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523 { BlackRook, BlackMan, BlackBishop, BlackQueen,
524 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackMan, BlackFerz,
552 BlackKing, BlackMan, BlackKnight, BlackRook }
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 #define GothicArray CapablancaArray
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 #define FalconArray CapablancaArray
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
640 Board initialPosition;
643 /* Convert str to a rating. Checks for special cases of "----",
645 "++++", etc. Also strips ()'s */
647 string_to_rating (char *str)
649 while(*str && !isdigit(*str)) ++str;
651 return 0; /* One of the special "no rating" cases */
659 /* Init programStats */
660 programStats.movelist[0] = 0;
661 programStats.depth = 0;
662 programStats.nr_moves = 0;
663 programStats.moves_left = 0;
664 programStats.nodes = 0;
665 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
666 programStats.score = 0;
667 programStats.got_only_move = 0;
668 programStats.got_fail = 0;
669 programStats.line_is_book = 0;
674 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675 if (appData.firstPlaysBlack) {
676 first.twoMachinesColor = "black\n";
677 second.twoMachinesColor = "white\n";
679 first.twoMachinesColor = "white\n";
680 second.twoMachinesColor = "black\n";
683 first.other = &second;
684 second.other = &first;
687 if(appData.timeOddsMode) {
688 norm = appData.timeOdds[0];
689 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691 first.timeOdds = appData.timeOdds[0]/norm;
692 second.timeOdds = appData.timeOdds[1]/norm;
695 if(programVersion) free(programVersion);
696 if (appData.noChessProgram) {
697 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698 sprintf(programVersion, "%s", PACKAGE_STRING);
700 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707 UnloadEngine (ChessProgramState *cps)
709 /* Kill off first chess program */
710 if (cps->isr != NULL)
711 RemoveInputSource(cps->isr);
714 if (cps->pr != NoProc) {
716 DoSleep( appData.delayBeforeQuit );
717 SendToProgram("quit\n", cps);
718 DoSleep( appData.delayAfterQuit );
719 DestroyChildProcess(cps->pr, cps->useSigterm);
722 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 ClearOptions (ChessProgramState *cps)
729 cps->nrOptions = cps->comboCnt = 0;
730 for(i=0; i<MAX_OPTIONS; i++) {
731 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732 cps->option[i].textValue = 0;
736 char *engineNames[] = {
737 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 InitEngine (ChessProgramState *cps, int n)
747 { // [HGM] all engine initialiation put in a function that does one engine
751 cps->which = engineNames[n];
752 cps->maybeThinking = FALSE;
756 cps->sendDrawOffers = 1;
758 cps->program = appData.chessProgram[n];
759 cps->host = appData.host[n];
760 cps->dir = appData.directory[n];
761 cps->initString = appData.engInitString[n];
762 cps->computerString = appData.computerString[n];
763 cps->useSigint = TRUE;
764 cps->useSigterm = TRUE;
765 cps->reuse = appData.reuse[n];
766 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
767 cps->useSetboard = FALSE;
769 cps->usePing = FALSE;
772 cps->usePlayother = FALSE;
773 cps->useColors = TRUE;
774 cps->useUsermove = FALSE;
775 cps->sendICS = FALSE;
776 cps->sendName = appData.icsActive;
777 cps->sdKludge = FALSE;
778 cps->stKludge = FALSE;
779 TidyProgramName(cps->program, cps->host, cps->tidy);
781 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782 cps->analysisSupport = 2; /* detect */
783 cps->analyzing = FALSE;
784 cps->initDone = FALSE;
786 /* New features added by Tord: */
787 cps->useFEN960 = FALSE;
788 cps->useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 cps->fenOverride = appData.fenOverride[n];
792 /* [HGM] time odds: set factor for each machine */
793 cps->timeOdds = appData.timeOdds[n];
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 cps->accumulateTC = appData.accumulateTC[n];
797 cps->maxNrOfSessions = 1;
802 cps->supportsNPS = UNKNOWN;
803 cps->memSize = FALSE;
804 cps->maxCores = FALSE;
805 cps->egtFormats[0] = NULLCHAR;
808 cps->optionSettings = appData.engOptions[n];
810 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811 cps->isUCI = appData.isUCI[n]; /* [AS] */
812 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814 if (appData.protocolVersion[n] > PROTOVER
815 || appData.protocolVersion[n] < 1)
820 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821 appData.protocolVersion[n]);
822 if( (len >= MSG_SIZ) && appData.debugMode )
823 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825 DisplayFatalError(buf, 0, 2);
829 cps->protocolVersion = appData.protocolVersion[n];
832 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
833 ParseFeatures(appData.featureDefaults, cps);
836 ChessProgramState *savCps;
842 if(WaitForEngine(savCps, LoadEngine)) return;
843 CommonEngineInit(); // recalculate time odds
844 if(gameInfo.variant != StringToVariant(appData.variant)) {
845 // we changed variant when loading the engine; this forces us to reset
846 Reset(TRUE, savCps != &first);
847 EditGameEvent(); // for consistency with other path, as Reset changes mode
849 InitChessProgram(savCps, FALSE);
850 SendToProgram("force\n", savCps);
851 DisplayMessage("", "");
852 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859 ReplaceEngine (ChessProgramState *cps, int n)
863 appData.noChessProgram = FALSE;
864 appData.clockMode = TRUE;
867 if(n) return; // only startup first engine immediately; second can wait
868 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875 static char resetOptions[] =
876 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
881 FloatToFront(char **list, char *engineLine)
883 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885 if(appData.recentEngines <= 0) return;
886 TidyProgramName(engineLine, "localhost", tidy+1);
887 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888 strncpy(buf+1, *list, MSG_SIZ-50);
889 if(p = strstr(buf, tidy)) { // tidy name appears in list
890 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891 while(*p++ = *++q); // squeeze out
893 strcat(tidy, buf+1); // put list behind tidy name
894 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896 ASSIGN(*list, tidy+1);
900 Load (ChessProgramState *cps, int i)
902 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907 appData.firstProtocolVersion = PROTOVER;
908 ParseArgsFromString(buf);
910 ReplaceEngine(cps, i);
911 FloatToFront(&appData.recentEngineList, engineLine);
915 while(q = strchr(p, SLASH)) p = q+1;
916 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917 if(engineDir[0] != NULLCHAR)
918 appData.directory[i] = engineDir;
919 else if(p != engineName) { // derive directory from engine path, when not given
921 appData.directory[i] = strdup(engineName);
923 } else appData.directory[i] = ".";
925 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926 snprintf(command, MSG_SIZ, "%s %s", p, params);
929 appData.chessProgram[i] = strdup(p);
930 appData.isUCI[i] = isUCI;
931 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932 appData.hasOwnBookUCI[i] = hasBook;
933 if(!nickName[0]) useNick = FALSE;
934 if(useNick) ASSIGN(appData.pgnName[i], nickName);
938 q = firstChessProgramNames;
939 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942 quote, p, quote, appData.directory[i],
943 useNick ? " -fn \"" : "",
944 useNick ? nickName : "",
946 v1 ? " -firstProtocolVersion 1" : "",
947 hasBook ? "" : " -fNoOwnBookUCI",
948 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949 storeVariant ? " -variant " : "",
950 storeVariant ? VariantName(gameInfo.variant) : "");
951 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
954 FloatToFront(&appData.recentEngineList, buf);
956 ReplaceEngine(cps, i);
962 int matched, min, sec;
964 * Parse timeControl resource
966 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967 appData.movesPerSession)) {
969 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970 DisplayFatalError(buf, 0, 2);
974 * Parse searchTime resource
976 if (*appData.searchTime != NULLCHAR) {
977 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
979 searchTime = min * 60;
980 } else if (matched == 2) {
981 searchTime = min * 60 + sec;
984 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985 DisplayFatalError(buf, 0, 2);
994 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
997 GetTimeMark(&programStartTime);
998 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999 appData.seedBase = random() + (random()<<15);
1000 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1002 ClearProgramStats();
1003 programStats.ok_to_send = 1;
1004 programStats.seen_stat = 0;
1007 * Initialize game list
1013 * Internet chess server status
1015 if (appData.icsActive) {
1016 appData.matchMode = FALSE;
1017 appData.matchGames = 0;
1019 appData.noChessProgram = !appData.zippyPlay;
1021 appData.zippyPlay = FALSE;
1022 appData.zippyTalk = FALSE;
1023 appData.noChessProgram = TRUE;
1025 if (*appData.icsHelper != NULLCHAR) {
1026 appData.useTelnet = TRUE;
1027 appData.telnetProgram = appData.icsHelper;
1030 appData.zippyTalk = appData.zippyPlay = FALSE;
1033 /* [AS] Initialize pv info list [HGM] and game state */
1037 for( i=0; i<=framePtr; i++ ) {
1038 pvInfoList[i].depth = -1;
1039 boards[i][EP_STATUS] = EP_NONE;
1040 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1046 /* [AS] Adjudication threshold */
1047 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1049 InitEngine(&first, 0);
1050 InitEngine(&second, 1);
1053 pairing.which = "pairing"; // pairing engine
1054 pairing.pr = NoProc;
1056 pairing.program = appData.pairingEngine;
1057 pairing.host = "localhost";
1060 if (appData.icsActive) {
1061 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1062 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063 appData.clockMode = FALSE;
1064 first.sendTime = second.sendTime = 0;
1068 /* Override some settings from environment variables, for backward
1069 compatibility. Unfortunately it's not feasible to have the env
1070 vars just set defaults, at least in xboard. Ugh.
1072 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1077 if (!appData.icsActive) {
1081 /* Check for variants that are supported only in ICS mode,
1082 or not at all. Some that are accepted here nevertheless
1083 have bugs; see comments below.
1085 VariantClass variant = StringToVariant(appData.variant);
1087 case VariantBughouse: /* need four players and two boards */
1088 case VariantKriegspiel: /* need to hide pieces and move details */
1089 /* case VariantFischeRandom: (Fabien: moved below) */
1090 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091 if( (len >= MSG_SIZ) && appData.debugMode )
1092 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094 DisplayFatalError(buf, 0, 2);
1097 case VariantUnknown:
1098 case VariantLoadable:
1108 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109 if( (len >= MSG_SIZ) && appData.debugMode )
1110 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112 DisplayFatalError(buf, 0, 2);
1115 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1116 case VariantFairy: /* [HGM] TestLegality definitely off! */
1117 case VariantGothic: /* [HGM] should work */
1118 case VariantCapablanca: /* [HGM] should work */
1119 case VariantCourier: /* [HGM] initial forced moves not implemented */
1120 case VariantShogi: /* [HGM] could still mate with pawn drop */
1121 case VariantKnightmate: /* [HGM] should work */
1122 case VariantCylinder: /* [HGM] untested */
1123 case VariantFalcon: /* [HGM] untested */
1124 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125 offboard interposition not understood */
1126 case VariantNormal: /* definitely works! */
1127 case VariantWildCastle: /* pieces not automatically shuffled */
1128 case VariantNoCastle: /* pieces not automatically shuffled */
1129 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130 case VariantLosers: /* should work except for win condition,
1131 and doesn't know captures are mandatory */
1132 case VariantSuicide: /* should work except for win condition,
1133 and doesn't know captures are mandatory */
1134 case VariantGiveaway: /* should work except for win condition,
1135 and doesn't know captures are mandatory */
1136 case VariantTwoKings: /* should work */
1137 case VariantAtomic: /* should work except for win condition */
1138 case Variant3Check: /* should work except for win condition */
1139 case VariantShatranj: /* should work except for all win conditions */
1140 case VariantMakruk: /* should work except for draw countdown */
1141 case VariantBerolina: /* might work if TestLegality is off */
1142 case VariantCapaRandom: /* should work */
1143 case VariantJanus: /* should work */
1144 case VariantSuper: /* experimental */
1145 case VariantGreat: /* experimental, requires legality testing to be off */
1146 case VariantSChess: /* S-Chess, should work */
1147 case VariantGrand: /* should work */
1148 case VariantSpartan: /* should work */
1156 NextIntegerFromString (char ** str, long * value)
1161 while( *s == ' ' || *s == '\t' ) {
1167 if( *s >= '0' && *s <= '9' ) {
1168 while( *s >= '0' && *s <= '9' ) {
1169 *value = *value * 10 + (*s - '0');
1182 NextTimeControlFromString (char ** str, long * value)
1185 int result = NextIntegerFromString( str, &temp );
1188 *value = temp * 60; /* Minutes */
1189 if( **str == ':' ) {
1191 result = NextIntegerFromString( str, &temp );
1192 *value += temp; /* Seconds */
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202 int result = -1, type = 0; long temp, temp2;
1204 if(**str != ':') return -1; // old params remain in force!
1206 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207 if( NextIntegerFromString( str, &temp ) ) return -1;
1208 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1211 /* time only: incremental or sudden-death time control */
1212 if(**str == '+') { /* increment follows; read it */
1214 if(**str == '!') type = *(*str)++; // Bronstein TC
1215 if(result = NextIntegerFromString( str, &temp2)) return -1;
1216 *inc = temp2 * 1000;
1217 if(**str == '.') { // read fraction of increment
1218 char *start = ++(*str);
1219 if(result = NextIntegerFromString( str, &temp2)) return -1;
1221 while(start++ < *str) temp2 /= 10;
1225 *moves = 0; *tc = temp * 1000; *incType = type;
1229 (*str)++; /* classical time control */
1230 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 { /* [HGM] get time to add from the multi-session time-control string */
1244 int incType, moves=1; /* kludge to force reading of first session */
1245 long time, increment;
1248 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1250 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252 if(movenr == -1) return time; /* last move before new session */
1253 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255 if(!moves) return increment; /* current session is incremental */
1256 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257 } while(movenr >= -1); /* try again for next session */
1259 return 0; // no new time quota on this move
1263 ParseTimeControl (char *tc, float ti, int mps)
1267 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1270 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1276 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1278 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1281 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1283 snprintf(buf, MSG_SIZ, ":%s", mytc);
1285 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1287 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1292 /* Parse second time control */
1295 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303 timeControl_2 = tc2 * 1000;
1313 timeControl = tc1 * 1000;
1316 timeIncrement = ti * 1000; /* convert to ms */
1317 movesPerSession = 0;
1320 movesPerSession = mps;
1328 if (appData.debugMode) {
1329 fprintf(debugFP, "%s\n", programVersion);
1331 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1333 set_cont_sequence(appData.wrapContSeq);
1334 if (appData.matchGames > 0) {
1335 appData.matchMode = TRUE;
1336 } else if (appData.matchMode) {
1337 appData.matchGames = 1;
1339 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340 appData.matchGames = appData.sameColorGames;
1341 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1346 if (appData.noChessProgram || first.protocolVersion == 1) {
1349 /* kludge: allow timeout for initial "feature" commands */
1351 DisplayMessage("", _("Starting chess program"));
1352 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1357 CalculateIndex (int index, int gameNr)
1358 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1360 if(index > 0) return index; // fixed nmber
1361 if(index == 0) return 1;
1362 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1368 LoadGameOrPosition (int gameNr)
1369 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370 if (*appData.loadGameFile != NULLCHAR) {
1371 if (!LoadGameFromFile(appData.loadGameFile,
1372 CalculateIndex(appData.loadGameIndex, gameNr),
1373 appData.loadGameFile, FALSE)) {
1374 DisplayFatalError(_("Bad game file"), 0, 1);
1377 } else if (*appData.loadPositionFile != NULLCHAR) {
1378 if (!LoadPositionFromFile(appData.loadPositionFile,
1379 CalculateIndex(appData.loadPositionIndex, gameNr),
1380 appData.loadPositionFile)) {
1381 DisplayFatalError(_("Bad position file"), 0, 1);
1389 ReserveGame (int gameNr, char resChar)
1391 FILE *tf = fopen(appData.tourneyFile, "r+");
1392 char *p, *q, c, buf[MSG_SIZ];
1393 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394 safeStrCpy(buf, lastMsg, MSG_SIZ);
1395 DisplayMessage(_("Pick new game"), "");
1396 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397 ParseArgsFromFile(tf);
1398 p = q = appData.results;
1399 if(appData.debugMode) {
1400 char *r = appData.participants;
1401 fprintf(debugFP, "results = '%s'\n", p);
1402 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403 fprintf(debugFP, "\n");
1405 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1407 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408 safeStrCpy(q, p, strlen(p) + 2);
1409 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1415 fseek(tf, -(strlen(p)+4), SEEK_END);
1417 if(c != '"') // depending on DOS or Unix line endings we can be one off
1418 fseek(tf, -(strlen(p)+2), SEEK_END);
1419 else fseek(tf, -(strlen(p)+3), SEEK_END);
1420 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421 DisplayMessage(buf, "");
1422 free(p); appData.results = q;
1423 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425 int round = appData.defaultMatchGames * appData.tourneyType;
1426 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1427 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428 UnloadEngine(&first); // next game belongs to other pairing;
1429 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1431 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1435 MatchEvent (int mode)
1436 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1438 if(matchMode) { // already in match mode: switch it off
1440 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1443 // if(gameMode != BeginningOfGame) {
1444 // DisplayError(_("You can only start a match from the initial position."), 0);
1448 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449 /* Set up machine vs. machine match */
1451 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452 if(appData.tourneyFile[0]) {
1454 if(nextGame > appData.matchGames) {
1456 if(strchr(appData.results, '*') == NULL) {
1458 appData.tourneyCycles++;
1459 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1461 NextTourneyGame(-1, &dummy);
1463 if(nextGame <= appData.matchGames) {
1464 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1466 ScheduleDelayedEvent(NextMatchGame, 10000);
1471 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472 DisplayError(buf, 0);
1473 appData.tourneyFile[0] = 0;
1477 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1478 DisplayFatalError(_("Can't have a match with no chess programs"),
1483 matchGame = roundNr = 1;
1484 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1488 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1491 InitBackEnd3 P((void))
1493 GameMode initialMode;
1497 InitChessProgram(&first, startedFromSetupPosition);
1499 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1500 free(programVersion);
1501 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1502 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1503 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1506 if (appData.icsActive) {
1508 /* [DM] Make a console window if needed [HGM] merged ifs */
1514 if (*appData.icsCommPort != NULLCHAR)
1515 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1516 appData.icsCommPort);
1518 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1519 appData.icsHost, appData.icsPort);
1521 if( (len >= MSG_SIZ) && appData.debugMode )
1522 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1524 DisplayFatalError(buf, err, 1);
1529 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1531 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1532 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1533 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1534 } else if (appData.noChessProgram) {
1540 if (*appData.cmailGameName != NULLCHAR) {
1542 OpenLoopback(&cmailPR);
1544 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1548 DisplayMessage("", "");
1549 if (StrCaseCmp(appData.initialMode, "") == 0) {
1550 initialMode = BeginningOfGame;
1551 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1552 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1553 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1554 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1557 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1558 initialMode = TwoMachinesPlay;
1559 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1560 initialMode = AnalyzeFile;
1561 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1562 initialMode = AnalyzeMode;
1563 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1564 initialMode = MachinePlaysWhite;
1565 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1566 initialMode = MachinePlaysBlack;
1567 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1568 initialMode = EditGame;
1569 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1570 initialMode = EditPosition;
1571 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1572 initialMode = Training;
1574 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1575 if( (len >= MSG_SIZ) && appData.debugMode )
1576 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1578 DisplayFatalError(buf, 0, 2);
1582 if (appData.matchMode) {
1583 if(appData.tourneyFile[0]) { // start tourney from command line
1585 if(f = fopen(appData.tourneyFile, "r")) {
1586 ParseArgsFromFile(f); // make sure tourney parmeters re known
1588 appData.clockMode = TRUE;
1590 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1593 } else if (*appData.cmailGameName != NULLCHAR) {
1594 /* Set up cmail mode */
1595 ReloadCmailMsgEvent(TRUE);
1597 /* Set up other modes */
1598 if (initialMode == AnalyzeFile) {
1599 if (*appData.loadGameFile == NULLCHAR) {
1600 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1604 if (*appData.loadGameFile != NULLCHAR) {
1605 (void) LoadGameFromFile(appData.loadGameFile,
1606 appData.loadGameIndex,
1607 appData.loadGameFile, TRUE);
1608 } else if (*appData.loadPositionFile != NULLCHAR) {
1609 (void) LoadPositionFromFile(appData.loadPositionFile,
1610 appData.loadPositionIndex,
1611 appData.loadPositionFile);
1612 /* [HGM] try to make self-starting even after FEN load */
1613 /* to allow automatic setup of fairy variants with wtm */
1614 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1615 gameMode = BeginningOfGame;
1616 setboardSpoiledMachineBlack = 1;
1618 /* [HGM] loadPos: make that every new game uses the setup */
1619 /* from file as long as we do not switch variant */
1620 if(!blackPlaysFirst) {
1621 startedFromPositionFile = TRUE;
1622 CopyBoard(filePosition, boards[0]);
1625 if (initialMode == AnalyzeMode) {
1626 if (appData.noChessProgram) {
1627 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1630 if (appData.icsActive) {
1631 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1635 } else if (initialMode == AnalyzeFile) {
1636 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1637 ShowThinkingEvent();
1639 AnalysisPeriodicEvent(1);
1640 } else if (initialMode == MachinePlaysWhite) {
1641 if (appData.noChessProgram) {
1642 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1646 if (appData.icsActive) {
1647 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1651 MachineWhiteEvent();
1652 } else if (initialMode == MachinePlaysBlack) {
1653 if (appData.noChessProgram) {
1654 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1658 if (appData.icsActive) {
1659 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1663 MachineBlackEvent();
1664 } else if (initialMode == TwoMachinesPlay) {
1665 if (appData.noChessProgram) {
1666 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1670 if (appData.icsActive) {
1671 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1676 } else if (initialMode == EditGame) {
1678 } else if (initialMode == EditPosition) {
1679 EditPositionEvent();
1680 } else if (initialMode == Training) {
1681 if (*appData.loadGameFile == NULLCHAR) {
1682 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1693 DisplayBook(current+1);
1695 MoveHistorySet( movelist, first, last, current, pvInfoList );
1697 EvalGraphSet( first, last, current, pvInfoList );
1699 MakeEngineOutputTitle();
1703 * Establish will establish a contact to a remote host.port.
1704 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1705 * used to talk to the host.
1706 * Returns 0 if okay, error code if not.
1713 if (*appData.icsCommPort != NULLCHAR) {
1714 /* Talk to the host through a serial comm port */
1715 return OpenCommPort(appData.icsCommPort, &icsPR);
1717 } else if (*appData.gateway != NULLCHAR) {
1718 if (*appData.remoteShell == NULLCHAR) {
1719 /* Use the rcmd protocol to run telnet program on a gateway host */
1720 snprintf(buf, sizeof(buf), "%s %s %s",
1721 appData.telnetProgram, appData.icsHost, appData.icsPort);
1722 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1725 /* Use the rsh program to run telnet program on a gateway host */
1726 if (*appData.remoteUser == NULLCHAR) {
1727 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1728 appData.gateway, appData.telnetProgram,
1729 appData.icsHost, appData.icsPort);
1731 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1732 appData.remoteShell, appData.gateway,
1733 appData.remoteUser, appData.telnetProgram,
1734 appData.icsHost, appData.icsPort);
1736 return StartChildProcess(buf, "", &icsPR);
1739 } else if (appData.useTelnet) {
1740 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1743 /* TCP socket interface differs somewhat between
1744 Unix and NT; handle details in the front end.
1746 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1751 EscapeExpand (char *p, char *q)
1752 { // [HGM] initstring: routine to shape up string arguments
1753 while(*p++ = *q++) if(p[-1] == '\\')
1755 case 'n': p[-1] = '\n'; break;
1756 case 'r': p[-1] = '\r'; break;
1757 case 't': p[-1] = '\t'; break;
1758 case '\\': p[-1] = '\\'; break;
1759 case 0: *p = 0; return;
1760 default: p[-1] = q[-1]; break;
1765 show_bytes (FILE *fp, char *buf, int count)
1768 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1769 fprintf(fp, "\\%03o", *buf & 0xff);
1778 /* Returns an errno value */
1780 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1782 char buf[8192], *p, *q, *buflim;
1783 int left, newcount, outcount;
1785 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1786 *appData.gateway != NULLCHAR) {
1787 if (appData.debugMode) {
1788 fprintf(debugFP, ">ICS: ");
1789 show_bytes(debugFP, message, count);
1790 fprintf(debugFP, "\n");
1792 return OutputToProcess(pr, message, count, outError);
1795 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1802 if (appData.debugMode) {
1803 fprintf(debugFP, ">ICS: ");
1804 show_bytes(debugFP, buf, newcount);
1805 fprintf(debugFP, "\n");
1807 outcount = OutputToProcess(pr, buf, newcount, outError);
1808 if (outcount < newcount) return -1; /* to be sure */
1815 } else if (((unsigned char) *p) == TN_IAC) {
1816 *q++ = (char) TN_IAC;
1823 if (appData.debugMode) {
1824 fprintf(debugFP, ">ICS: ");
1825 show_bytes(debugFP, buf, newcount);
1826 fprintf(debugFP, "\n");
1828 outcount = OutputToProcess(pr, buf, newcount, outError);
1829 if (outcount < newcount) return -1; /* to be sure */
1834 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1836 int outError, outCount;
1837 static int gotEof = 0;
1839 /* Pass data read from player on to ICS */
1842 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1843 if (outCount < count) {
1844 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846 } else if (count < 0) {
1847 RemoveInputSource(isr);
1848 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1849 } else if (gotEof++ > 0) {
1850 RemoveInputSource(isr);
1851 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1857 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1858 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1859 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1860 SendToICS("date\n");
1861 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1864 /* added routine for printf style output to ics */
1866 ics_printf (char *format, ...)
1868 char buffer[MSG_SIZ];
1871 va_start(args, format);
1872 vsnprintf(buffer, sizeof(buffer), format, args);
1873 buffer[sizeof(buffer)-1] = '\0';
1881 int count, outCount, outError;
1883 if (icsPR == NoProc) return;
1886 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1887 if (outCount < count) {
1888 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892 /* This is used for sending logon scripts to the ICS. Sending
1893 without a delay causes problems when using timestamp on ICC
1894 (at least on my machine). */
1896 SendToICSDelayed (char *s, long msdelay)
1898 int count, outCount, outError;
1900 if (icsPR == NoProc) return;
1903 if (appData.debugMode) {
1904 fprintf(debugFP, ">ICS: ");
1905 show_bytes(debugFP, s, count);
1906 fprintf(debugFP, "\n");
1908 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1910 if (outCount < count) {
1911 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916 /* Remove all highlighting escape sequences in s
1917 Also deletes any suffix starting with '('
1920 StripHighlightAndTitle (char *s)
1922 static char retbuf[MSG_SIZ];
1925 while (*s != NULLCHAR) {
1926 while (*s == '\033') {
1927 while (*s != NULLCHAR && !isalpha(*s)) s++;
1928 if (*s != NULLCHAR) s++;
1930 while (*s != NULLCHAR && *s != '\033') {
1931 if (*s == '(' || *s == '[') {
1942 /* Remove all highlighting escape sequences in s */
1944 StripHighlight (char *s)
1946 static char retbuf[MSG_SIZ];
1949 while (*s != NULLCHAR) {
1950 while (*s == '\033') {
1951 while (*s != NULLCHAR && !isalpha(*s)) s++;
1952 if (*s != NULLCHAR) s++;
1954 while (*s != NULLCHAR && *s != '\033') {
1962 char *variantNames[] = VARIANT_NAMES;
1964 VariantName (VariantClass v)
1966 return variantNames[v];
1970 /* Identify a variant from the strings the chess servers use or the
1971 PGN Variant tag names we use. */
1973 StringToVariant (char *e)
1977 VariantClass v = VariantNormal;
1978 int i, found = FALSE;
1984 /* [HGM] skip over optional board-size prefixes */
1985 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1986 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1987 while( *e++ != '_');
1990 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1994 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1995 if (StrCaseStr(e, variantNames[i])) {
1996 v = (VariantClass) i;
2003 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2004 || StrCaseStr(e, "wild/fr")
2005 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2006 v = VariantFischeRandom;
2007 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2008 (i = 1, p = StrCaseStr(e, "w"))) {
2010 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2017 case 0: /* FICS only, actually */
2019 /* Castling legal even if K starts on d-file */
2020 v = VariantWildCastle;
2025 /* Castling illegal even if K & R happen to start in
2026 normal positions. */
2027 v = VariantNoCastle;
2040 /* Castling legal iff K & R start in normal positions */
2046 /* Special wilds for position setup; unclear what to do here */
2047 v = VariantLoadable;
2050 /* Bizarre ICC game */
2051 v = VariantTwoKings;
2054 v = VariantKriegspiel;
2060 v = VariantFischeRandom;
2063 v = VariantCrazyhouse;
2066 v = VariantBughouse;
2072 /* Not quite the same as FICS suicide! */
2073 v = VariantGiveaway;
2079 v = VariantShatranj;
2082 /* Temporary names for future ICC types. The name *will* change in
2083 the next xboard/WinBoard release after ICC defines it. */
2121 v = VariantCapablanca;
2124 v = VariantKnightmate;
2130 v = VariantCylinder;
2136 v = VariantCapaRandom;
2139 v = VariantBerolina;
2151 /* Found "wild" or "w" in the string but no number;
2152 must assume it's normal chess. */
2156 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2157 if( (len >= MSG_SIZ) && appData.debugMode )
2158 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2160 DisplayError(buf, 0);
2166 if (appData.debugMode) {
2167 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2168 e, wnum, VariantName(v));
2173 static int leftover_start = 0, leftover_len = 0;
2174 char star_match[STAR_MATCH_N][MSG_SIZ];
2176 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2177 advance *index beyond it, and set leftover_start to the new value of
2178 *index; else return FALSE. If pattern contains the character '*', it
2179 matches any sequence of characters not containing '\r', '\n', or the
2180 character following the '*' (if any), and the matched sequence(s) are
2181 copied into star_match.
2184 looking_at ( char *buf, int *index, char *pattern)
2186 char *bufp = &buf[*index], *patternp = pattern;
2188 char *matchp = star_match[0];
2191 if (*patternp == NULLCHAR) {
2192 *index = leftover_start = bufp - buf;
2196 if (*bufp == NULLCHAR) return FALSE;
2197 if (*patternp == '*') {
2198 if (*bufp == *(patternp + 1)) {
2200 matchp = star_match[++star_count];
2204 } else if (*bufp == '\n' || *bufp == '\r') {
2206 if (*patternp == NULLCHAR)
2211 *matchp++ = *bufp++;
2215 if (*patternp != *bufp) return FALSE;
2222 SendToPlayer (char *data, int length)
2224 int error, outCount;
2225 outCount = OutputToProcess(NoProc, data, length, &error);
2226 if (outCount < length) {
2227 DisplayFatalError(_("Error writing to display"), error, 1);
2232 PackHolding (char packed[], char *holding)
2242 switch (runlength) {
2253 sprintf(q, "%d", runlength);
2265 /* Telnet protocol requests from the front end */
2267 TelnetRequest (unsigned char ddww, unsigned char option)
2269 unsigned char msg[3];
2270 int outCount, outError;
2272 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2274 if (appData.debugMode) {
2275 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2291 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2303 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2308 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2310 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2317 if (!appData.icsActive) return;
2318 TelnetRequest(TN_DO, TN_ECHO);
2324 if (!appData.icsActive) return;
2325 TelnetRequest(TN_DONT, TN_ECHO);
2329 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2331 /* put the holdings sent to us by the server on the board holdings area */
2332 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2336 if(gameInfo.holdingsWidth < 2) return;
2337 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338 return; // prevent overwriting by pre-board holdings
2340 if( (int)lowestPiece >= BlackPawn ) {
2343 holdingsStartRow = BOARD_HEIGHT-1;
2346 holdingsColumn = BOARD_WIDTH-1;
2347 countsColumn = BOARD_WIDTH-2;
2348 holdingsStartRow = 0;
2352 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353 board[i][holdingsColumn] = EmptySquare;
2354 board[i][countsColumn] = (ChessSquare) 0;
2356 while( (p=*holdings++) != NULLCHAR ) {
2357 piece = CharToPiece( ToUpper(p) );
2358 if(piece == EmptySquare) continue;
2359 /*j = (int) piece - (int) WhitePawn;*/
2360 j = PieceToNumber(piece);
2361 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362 if(j < 0) continue; /* should not happen */
2363 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365 board[holdingsStartRow+j*direction][countsColumn]++;
2371 VariantSwitch (Board board, VariantClass newVariant)
2373 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374 static Board oldBoard;
2376 startedFromPositionFile = FALSE;
2377 if(gameInfo.variant == newVariant) return;
2379 /* [HGM] This routine is called each time an assignment is made to
2380 * gameInfo.variant during a game, to make sure the board sizes
2381 * are set to match the new variant. If that means adding or deleting
2382 * holdings, we shift the playing board accordingly
2383 * This kludge is needed because in ICS observe mode, we get boards
2384 * of an ongoing game without knowing the variant, and learn about the
2385 * latter only later. This can be because of the move list we requested,
2386 * in which case the game history is refilled from the beginning anyway,
2387 * but also when receiving holdings of a crazyhouse game. In the latter
2388 * case we want to add those holdings to the already received position.
2392 if (appData.debugMode) {
2393 fprintf(debugFP, "Switch board from %s to %s\n",
2394 VariantName(gameInfo.variant), VariantName(newVariant));
2395 setbuf(debugFP, NULL);
2397 shuffleOpenings = 0; /* [HGM] shuffle */
2398 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2402 newWidth = 9; newHeight = 9;
2403 gameInfo.holdingsSize = 7;
2404 case VariantBughouse:
2405 case VariantCrazyhouse:
2406 newHoldingsWidth = 2; break;
2410 newHoldingsWidth = 2;
2411 gameInfo.holdingsSize = 8;
2414 case VariantCapablanca:
2415 case VariantCapaRandom:
2418 newHoldingsWidth = gameInfo.holdingsSize = 0;
2421 if(newWidth != gameInfo.boardWidth ||
2422 newHeight != gameInfo.boardHeight ||
2423 newHoldingsWidth != gameInfo.holdingsWidth ) {
2425 /* shift position to new playing area, if needed */
2426 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427 for(i=0; i<BOARD_HEIGHT; i++)
2428 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431 for(i=0; i<newHeight; i++) {
2432 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2435 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436 for(i=0; i<BOARD_HEIGHT; i++)
2437 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2441 gameInfo.boardWidth = newWidth;
2442 gameInfo.boardHeight = newHeight;
2443 gameInfo.holdingsWidth = newHoldingsWidth;
2444 gameInfo.variant = newVariant;
2445 InitDrawingSizes(-2, 0);
2446 } else gameInfo.variant = newVariant;
2447 CopyBoard(oldBoard, board); // remember correctly formatted board
2448 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2449 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2452 static int loggedOn = FALSE;
2454 /*-- Game start info cache: --*/
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\ ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2484 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486 if(r < minRating+100 && r >=0 ) r = minRating+100;
2487 if(r > maxRating) r = maxRating;
2488 if(tc < 1.) tc = 1.;
2489 if(tc > 95.) tc = 95.;
2490 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491 y = ((double)r - minRating)/(maxRating - minRating)
2492 * (h-vMargin-squareSize/8-1) + vMargin;
2493 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494 if(strstr(seekAdList[i], " u ")) color = 1;
2495 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496 !strstr(seekAdList[i], "bullet") &&
2497 !strstr(seekAdList[i], "blitz") &&
2498 !strstr(seekAdList[i], "standard") ) color = 2;
2499 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2504 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2506 char buf[MSG_SIZ], *ext = "";
2507 VariantClass v = StringToVariant(type);
2508 if(strstr(type, "wild")) {
2509 ext = type + 4; // append wild number
2510 if(v == VariantFischeRandom) type = "chess960"; else
2511 if(v == VariantLoadable) type = "setup"; else
2512 type = VariantName(v);
2514 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520 seekNrList[nrOfSeekAds] = nr;
2521 zList[nrOfSeekAds] = 0;
2522 seekAdList[nrOfSeekAds++] = StrSave(buf);
2523 if(plot) PlotSeekAd(nrOfSeekAds-1);
2528 EraseSeekDot (int i)
2530 int x = xList[i], y = yList[i], d=squareSize/4, k;
2531 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533 // now replot every dot that overlapped
2534 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535 int xx = xList[k], yy = yList[k];
2536 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537 DrawSeekDot(xx, yy, colorList[k]);
2542 RemoveSeekAd (int nr)
2545 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2547 if(seekAdList[i]) free(seekAdList[i]);
2548 seekAdList[i] = seekAdList[--nrOfSeekAds];
2549 seekNrList[i] = seekNrList[nrOfSeekAds];
2550 ratingList[i] = ratingList[nrOfSeekAds];
2551 colorList[i] = colorList[nrOfSeekAds];
2552 tcList[i] = tcList[nrOfSeekAds];
2553 xList[i] = xList[nrOfSeekAds];
2554 yList[i] = yList[nrOfSeekAds];
2555 zList[i] = zList[nrOfSeekAds];
2556 seekAdList[nrOfSeekAds] = NULL;
2562 MatchSoughtLine (char *line)
2564 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565 int nr, base, inc, u=0; char dummy;
2567 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2570 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2572 // match: compact and save the line
2573 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583 if(!seekGraphUp) return FALSE;
2584 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2587 DrawSeekBackground(0, 0, w, h);
2588 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2593 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2596 snprintf(buf, MSG_SIZ, "%d", i);
2597 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2600 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601 for(i=1; i<100; i+=(i<10?1:5)) {
2602 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2606 snprintf(buf, MSG_SIZ, "%d", i);
2607 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2610 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2615 SeekGraphClick (ClickType click, int x, int y, int moving)
2617 static int lastDown = 0, displayed = 0, lastSecond;
2618 if(y < 0) return FALSE;
2619 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2620 if(click == Release || moving) return FALSE;
2622 soughtPending = TRUE;
2623 SendToICS(ics_prefix);
2624 SendToICS("sought\n"); // should this be "sought all"?
2625 } else { // issue challenge based on clicked ad
2626 int dist = 10000; int i, closest = 0, second = 0;
2627 for(i=0; i<nrOfSeekAds; i++) {
2628 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2629 if(d < dist) { dist = d; closest = i; }
2630 second += (d - zList[i] < 120); // count in-range ads
2631 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2635 second = (second > 1);
2636 if(displayed != closest || second != lastSecond) {
2637 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2638 lastSecond = second; displayed = closest;
2640 if(click == Press) {
2641 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2644 } // on press 'hit', only show info
2645 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2646 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2647 SendToICS(ics_prefix);
2649 return TRUE; // let incoming board of started game pop down the graph
2650 } else if(click == Release) { // release 'miss' is ignored
2651 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2652 if(moving == 2) { // right up-click
2653 nrOfSeekAds = 0; // refresh graph
2654 soughtPending = TRUE;
2655 SendToICS(ics_prefix);
2656 SendToICS("sought\n"); // should this be "sought all"?
2659 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2660 // press miss or release hit 'pop down' seek graph
2661 seekGraphUp = FALSE;
2662 DrawPosition(TRUE, NULL);
2668 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2670 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2671 #define STARTED_NONE 0
2672 #define STARTED_MOVES 1
2673 #define STARTED_BOARD 2
2674 #define STARTED_OBSERVE 3
2675 #define STARTED_HOLDINGS 4
2676 #define STARTED_CHATTER 5
2677 #define STARTED_COMMENT 6
2678 #define STARTED_MOVES_NOHIDE 7
2680 static int started = STARTED_NONE;
2681 static char parse[20000];
2682 static int parse_pos = 0;
2683 static char buf[BUF_SIZE + 1];
2684 static int firstTime = TRUE, intfSet = FALSE;
2685 static ColorClass prevColor = ColorNormal;
2686 static int savingComment = FALSE;
2687 static int cmatch = 0; // continuation sequence match
2694 int backup; /* [DM] For zippy color lines */
2696 char talker[MSG_SIZ]; // [HGM] chat
2699 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2701 if (appData.debugMode) {
2703 fprintf(debugFP, "<ICS: ");
2704 show_bytes(debugFP, data, count);
2705 fprintf(debugFP, "\n");
2709 if (appData.debugMode) { int f = forwardMostMove;
2710 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2711 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2712 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2715 /* If last read ended with a partial line that we couldn't parse,
2716 prepend it to the new read and try again. */
2717 if (leftover_len > 0) {
2718 for (i=0; i<leftover_len; i++)
2719 buf[i] = buf[leftover_start + i];
2722 /* copy new characters into the buffer */
2723 bp = buf + leftover_len;
2724 buf_len=leftover_len;
2725 for (i=0; i<count; i++)
2728 if (data[i] == '\r')
2731 // join lines split by ICS?
2732 if (!appData.noJoin)
2735 Joining just consists of finding matches against the
2736 continuation sequence, and discarding that sequence
2737 if found instead of copying it. So, until a match
2738 fails, there's nothing to do since it might be the
2739 complete sequence, and thus, something we don't want
2742 if (data[i] == cont_seq[cmatch])
2745 if (cmatch == strlen(cont_seq))
2747 cmatch = 0; // complete match. just reset the counter
2750 it's possible for the ICS to not include the space
2751 at the end of the last word, making our [correct]
2752 join operation fuse two separate words. the server
2753 does this when the space occurs at the width setting.
2755 if (!buf_len || buf[buf_len-1] != ' ')
2766 match failed, so we have to copy what matched before
2767 falling through and copying this character. In reality,
2768 this will only ever be just the newline character, but
2769 it doesn't hurt to be precise.
2771 strncpy(bp, cont_seq, cmatch);
2783 buf[buf_len] = NULLCHAR;
2784 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2789 while (i < buf_len) {
2790 /* Deal with part of the TELNET option negotiation
2791 protocol. We refuse to do anything beyond the
2792 defaults, except that we allow the WILL ECHO option,
2793 which ICS uses to turn off password echoing when we are
2794 directly connected to it. We reject this option
2795 if localLineEditing mode is on (always on in xboard)
2796 and we are talking to port 23, which might be a real
2797 telnet server that will try to keep WILL ECHO on permanently.
2799 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2800 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2801 unsigned char option;
2803 switch ((unsigned char) buf[++i]) {
2805 if (appData.debugMode)
2806 fprintf(debugFP, "\n<WILL ");
2807 switch (option = (unsigned char) buf[++i]) {
2809 if (appData.debugMode)
2810 fprintf(debugFP, "ECHO ");
2811 /* Reply only if this is a change, according
2812 to the protocol rules. */
2813 if (remoteEchoOption) break;
2814 if (appData.localLineEditing &&
2815 atoi(appData.icsPort) == TN_PORT) {
2816 TelnetRequest(TN_DONT, TN_ECHO);
2819 TelnetRequest(TN_DO, TN_ECHO);
2820 remoteEchoOption = TRUE;
2824 if (appData.debugMode)
2825 fprintf(debugFP, "%d ", option);
2826 /* Whatever this is, we don't want it. */
2827 TelnetRequest(TN_DONT, option);
2832 if (appData.debugMode)
2833 fprintf(debugFP, "\n<WONT ");
2834 switch (option = (unsigned char) buf[++i]) {
2836 if (appData.debugMode)
2837 fprintf(debugFP, "ECHO ");
2838 /* Reply only if this is a change, according
2839 to the protocol rules. */
2840 if (!remoteEchoOption) break;
2842 TelnetRequest(TN_DONT, TN_ECHO);
2843 remoteEchoOption = FALSE;
2846 if (appData.debugMode)
2847 fprintf(debugFP, "%d ", (unsigned char) option);
2848 /* Whatever this is, it must already be turned
2849 off, because we never agree to turn on
2850 anything non-default, so according to the
2851 protocol rules, we don't reply. */
2856 if (appData.debugMode)
2857 fprintf(debugFP, "\n<DO ");
2858 switch (option = (unsigned char) buf[++i]) {
2860 /* Whatever this is, we refuse to do it. */
2861 if (appData.debugMode)
2862 fprintf(debugFP, "%d ", option);
2863 TelnetRequest(TN_WONT, option);
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<DONT ");
2870 switch (option = (unsigned char) buf[++i]) {
2872 if (appData.debugMode)
2873 fprintf(debugFP, "%d ", option);
2874 /* Whatever this is, we are already not doing
2875 it, because we never agree to do anything
2876 non-default, so according to the protocol
2877 rules, we don't reply. */
2882 if (appData.debugMode)
2883 fprintf(debugFP, "\n<IAC ");
2884 /* Doubled IAC; pass it through */
2888 if (appData.debugMode)
2889 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2890 /* Drop all other telnet commands on the floor */
2893 if (oldi > next_out)
2894 SendToPlayer(&buf[next_out], oldi - next_out);
2900 /* OK, this at least will *usually* work */
2901 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2905 if (loggedOn && !intfSet) {
2906 if (ics_type == ICS_ICC) {
2907 snprintf(str, MSG_SIZ,
2908 "/set-quietly interface %s\n/set-quietly style 12\n",
2910 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911 strcat(str, "/set-2 51 1\n/set seek 1\n");
2912 } else if (ics_type == ICS_CHESSNET) {
2913 snprintf(str, MSG_SIZ, "/style 12\n");
2915 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2916 strcat(str, programVersion);
2917 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2918 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2921 strcat(str, "$iset nohighlight 1\n");
2923 strcat(str, "$iset lock 1\n$style 12\n");
2926 NotifyFrontendLogin();
2930 if (started == STARTED_COMMENT) {
2931 /* Accumulate characters in comment */
2932 parse[parse_pos++] = buf[i];
2933 if (buf[i] == '\n') {
2934 parse[parse_pos] = NULLCHAR;
2935 if(chattingPartner>=0) {
2937 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2938 OutputChatMessage(chattingPartner, mess);
2939 chattingPartner = -1;
2940 next_out = i+1; // [HGM] suppress printing in ICS window
2942 if(!suppressKibitz) // [HGM] kibitz
2943 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2944 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2945 int nrDigit = 0, nrAlph = 0, j;
2946 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2947 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2948 parse[parse_pos] = NULLCHAR;
2949 // try to be smart: if it does not look like search info, it should go to
2950 // ICS interaction window after all, not to engine-output window.
2951 for(j=0; j<parse_pos; j++) { // count letters and digits
2952 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2953 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2954 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2956 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2957 int depth=0; float score;
2958 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2959 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2960 pvInfoList[forwardMostMove-1].depth = depth;
2961 pvInfoList[forwardMostMove-1].score = 100*score;
2963 OutputKibitz(suppressKibitz, parse);
2966 if(gameMode == IcsObserving) // restore original ICS messages
2967 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2969 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970 SendToPlayer(tmp, strlen(tmp));
2972 next_out = i+1; // [HGM] suppress printing in ICS window
2974 started = STARTED_NONE;
2976 /* Don't match patterns against characters in comment */
2981 if (started == STARTED_CHATTER) {
2982 if (buf[i] != '\n') {
2983 /* Don't match patterns against characters in chatter */
2987 started = STARTED_NONE;
2988 if(suppressKibitz) next_out = i+1;
2991 /* Kludge to deal with rcmd protocol */
2992 if (firstTime && looking_at(buf, &i, "\001*")) {
2993 DisplayFatalError(&buf[1], 0, 1);
2999 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3002 if (appData.debugMode)
3003 fprintf(debugFP, "ics_type %d\n", ics_type);
3006 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007 ics_type = ICS_FICS;
3009 if (appData.debugMode)
3010 fprintf(debugFP, "ics_type %d\n", ics_type);
3013 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014 ics_type = ICS_CHESSNET;
3016 if (appData.debugMode)
3017 fprintf(debugFP, "ics_type %d\n", ics_type);
3022 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023 looking_at(buf, &i, "Logging you in as \"*\"") ||
3024 looking_at(buf, &i, "will be \"*\""))) {
3025 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3029 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3031 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032 DisplayIcsInteractionTitle(buf);
3033 have_set_title = TRUE;
3036 /* skip finger notes */
3037 if (started == STARTED_NONE &&
3038 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039 (buf[i] == '1' && buf[i+1] == '0')) &&
3040 buf[i+2] == ':' && buf[i+3] == ' ') {
3041 started = STARTED_CHATTER;
3047 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048 if(appData.seekGraph) {
3049 if(soughtPending && MatchSoughtLine(buf+i)) {
3050 i = strstr(buf+i, "rated") - buf;
3051 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052 next_out = leftover_start = i;
3053 started = STARTED_CHATTER;
3054 suppressKibitz = TRUE;
3057 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058 && looking_at(buf, &i, "* ads displayed")) {
3059 soughtPending = FALSE;
3064 if(appData.autoRefresh) {
3065 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066 int s = (ics_type == ICS_ICC); // ICC format differs
3068 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070 looking_at(buf, &i, "*% "); // eat prompt
3071 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073 next_out = i; // suppress
3076 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077 char *p = star_match[0];
3079 if(seekGraphUp) RemoveSeekAd(atoi(p));
3080 while(*p && *p++ != ' '); // next
3082 looking_at(buf, &i, "*% "); // eat prompt
3083 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090 /* skip formula vars */
3091 if (started == STARTED_NONE &&
3092 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093 started = STARTED_CHATTER;
3098 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099 if (appData.autoKibitz && started == STARTED_NONE &&
3100 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3101 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3103 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3104 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3105 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3106 suppressKibitz = TRUE;
3107 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3109 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3110 && (gameMode == IcsPlayingWhite)) ||
3111 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3112 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3113 started = STARTED_CHATTER; // own kibitz we simply discard
3115 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3116 parse_pos = 0; parse[0] = NULLCHAR;
3117 savingComment = TRUE;
3118 suppressKibitz = gameMode != IcsObserving ? 2 :
3119 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3123 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3124 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3125 && atoi(star_match[0])) {
3126 // suppress the acknowledgements of our own autoKibitz
3128 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3129 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3130 SendToPlayer(star_match[0], strlen(star_match[0]));
3131 if(looking_at(buf, &i, "*% ")) // eat prompt
3132 suppressKibitz = FALSE;
3136 } // [HGM] kibitz: end of patch
3138 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3140 // [HGM] chat: intercept tells by users for which we have an open chat window
3142 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3143 looking_at(buf, &i, "* whispers:") ||
3144 looking_at(buf, &i, "* kibitzes:") ||
3145 looking_at(buf, &i, "* shouts:") ||
3146 looking_at(buf, &i, "* c-shouts:") ||
3147 looking_at(buf, &i, "--> * ") ||
3148 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3149 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3150 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3151 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3153 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3154 chattingPartner = -1;
3156 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3157 for(p=0; p<MAX_CHAT; p++) {
3158 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3159 talker[0] = '['; strcat(talker, "] ");
3160 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3161 chattingPartner = p; break;
3164 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3165 for(p=0; p<MAX_CHAT; p++) {
3166 if(!strcmp("kibitzes", chatPartner[p])) {
3167 talker[0] = '['; strcat(talker, "] ");
3168 chattingPartner = p; break;
3171 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(!strcmp("whispers", chatPartner[p])) {
3174 talker[0] = '['; strcat(talker, "] ");
3175 chattingPartner = p; break;
3178 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3179 if(buf[i-8] == '-' && buf[i-3] == 't')
3180 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3181 if(!strcmp("c-shouts", chatPartner[p])) {
3182 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3183 chattingPartner = p; break;
3186 if(chattingPartner < 0)
3187 for(p=0; p<MAX_CHAT; p++) {
3188 if(!strcmp("shouts", chatPartner[p])) {
3189 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3190 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3191 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3192 chattingPartner = p; break;
3196 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3197 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3198 talker[0] = 0; Colorize(ColorTell, FALSE);
3199 chattingPartner = p; break;
3201 if(chattingPartner<0) i = oldi; else {
3202 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3203 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3204 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3205 started = STARTED_COMMENT;
3206 parse_pos = 0; parse[0] = NULLCHAR;
3207 savingComment = 3 + chattingPartner; // counts as TRUE
3208 suppressKibitz = TRUE;
3211 } // [HGM] chat: end of patch
3214 if (appData.zippyTalk || appData.zippyPlay) {
3215 /* [DM] Backup address for color zippy lines */
3217 if (loggedOn == TRUE)
3218 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3219 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3221 } // [DM] 'else { ' deleted
3223 /* Regular tells and says */
3224 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3225 looking_at(buf, &i, "* (your partner) tells you: ") ||
3226 looking_at(buf, &i, "* says: ") ||
3227 /* Don't color "message" or "messages" output */
3228 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3229 looking_at(buf, &i, "*. * at *:*: ") ||
3230 looking_at(buf, &i, "--* (*:*): ") ||
3231 /* Message notifications (same color as tells) */
3232 looking_at(buf, &i, "* has left a message ") ||
3233 looking_at(buf, &i, "* just sent you a message:\n") ||
3234 /* Whispers and kibitzes */
3235 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3236 looking_at(buf, &i, "* kibitzes: ") ||
3238 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3240 if (tkind == 1 && strchr(star_match[0], ':')) {
3241 /* Avoid "tells you:" spoofs in channels */
3244 if (star_match[0][0] == NULLCHAR ||
3245 strchr(star_match[0], ' ') ||
3246 (tkind == 3 && strchr(star_match[1], ' '))) {
3247 /* Reject bogus matches */
3250 if (appData.colorize) {
3251 if (oldi > next_out) {
3252 SendToPlayer(&buf[next_out], oldi - next_out);
3257 Colorize(ColorTell, FALSE);
3258 curColor = ColorTell;
3261 Colorize(ColorKibitz, FALSE);
3262 curColor = ColorKibitz;
3265 p = strrchr(star_match[1], '(');
3272 Colorize(ColorChannel1, FALSE);
3273 curColor = ColorChannel1;
3275 Colorize(ColorChannel, FALSE);
3276 curColor = ColorChannel;
3280 curColor = ColorNormal;
3284 if (started == STARTED_NONE && appData.autoComment &&
3285 (gameMode == IcsObserving ||
3286 gameMode == IcsPlayingWhite ||
3287 gameMode == IcsPlayingBlack)) {
3288 parse_pos = i - oldi;
3289 memcpy(parse, &buf[oldi], parse_pos);
3290 parse[parse_pos] = NULLCHAR;
3291 started = STARTED_COMMENT;
3292 savingComment = TRUE;
3294 started = STARTED_CHATTER;
3295 savingComment = FALSE;
3302 if (looking_at(buf, &i, "* s-shouts: ") ||
3303 looking_at(buf, &i, "* c-shouts: ")) {
3304 if (appData.colorize) {
3305 if (oldi > next_out) {
3306 SendToPlayer(&buf[next_out], oldi - next_out);
3309 Colorize(ColorSShout, FALSE);
3310 curColor = ColorSShout;
3313 started = STARTED_CHATTER;
3317 if (looking_at(buf, &i, "--->")) {
3322 if (looking_at(buf, &i, "* shouts: ") ||
3323 looking_at(buf, &i, "--> ")) {
3324 if (appData.colorize) {
3325 if (oldi > next_out) {
3326 SendToPlayer(&buf[next_out], oldi - next_out);
3329 Colorize(ColorShout, FALSE);
3330 curColor = ColorShout;
3333 started = STARTED_CHATTER;
3337 if (looking_at( buf, &i, "Challenge:")) {
3338 if (appData.colorize) {
3339 if (oldi > next_out) {
3340 SendToPlayer(&buf[next_out], oldi - next_out);
3343 Colorize(ColorChallenge, FALSE);
3344 curColor = ColorChallenge;
3350 if (looking_at(buf, &i, "* offers you") ||
3351 looking_at(buf, &i, "* offers to be") ||
3352 looking_at(buf, &i, "* would like to") ||
3353 looking_at(buf, &i, "* requests to") ||
3354 looking_at(buf, &i, "Your opponent offers") ||
3355 looking_at(buf, &i, "Your opponent requests")) {
3357 if (appData.colorize) {
3358 if (oldi > next_out) {
3359 SendToPlayer(&buf[next_out], oldi - next_out);
3362 Colorize(ColorRequest, FALSE);
3363 curColor = ColorRequest;
3368 if (looking_at(buf, &i, "* (*) seeking")) {
3369 if (appData.colorize) {
3370 if (oldi > next_out) {
3371 SendToPlayer(&buf[next_out], oldi - next_out);
3374 Colorize(ColorSeek, FALSE);
3375 curColor = ColorSeek;
3380 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3382 if (looking_at(buf, &i, "\\ ")) {
3383 if (prevColor != ColorNormal) {
3384 if (oldi > next_out) {
3385 SendToPlayer(&buf[next_out], oldi - next_out);
3388 Colorize(prevColor, TRUE);
3389 curColor = prevColor;
3391 if (savingComment) {
3392 parse_pos = i - oldi;
3393 memcpy(parse, &buf[oldi], parse_pos);
3394 parse[parse_pos] = NULLCHAR;
3395 started = STARTED_COMMENT;
3396 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3397 chattingPartner = savingComment - 3; // kludge to remember the box
3399 started = STARTED_CHATTER;
3404 if (looking_at(buf, &i, "Black Strength :") ||
3405 looking_at(buf, &i, "<<< style 10 board >>>") ||
3406 looking_at(buf, &i, "<10>") ||
3407 looking_at(buf, &i, "#@#")) {
3408 /* Wrong board style */
3410 SendToICS(ics_prefix);
3411 SendToICS("set style 12\n");
3412 SendToICS(ics_prefix);
3413 SendToICS("refresh\n");
3417 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3419 have_sent_ICS_logon = 1;
3423 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3424 (looking_at(buf, &i, "\n<12> ") ||
3425 looking_at(buf, &i, "<12> "))) {
3427 if (oldi > next_out) {
3428 SendToPlayer(&buf[next_out], oldi - next_out);
3431 started = STARTED_BOARD;
3436 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3437 looking_at(buf, &i, "<b1> ")) {
3438 if (oldi > next_out) {
3439 SendToPlayer(&buf[next_out], oldi - next_out);
3442 started = STARTED_HOLDINGS;
3447 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3449 /* Header for a move list -- first line */
3451 switch (ics_getting_history) {
3455 case BeginningOfGame:
3456 /* User typed "moves" or "oldmoves" while we
3457 were idle. Pretend we asked for these
3458 moves and soak them up so user can step
3459 through them and/or save them.
3462 gameMode = IcsObserving;
3465 ics_getting_history = H_GOT_UNREQ_HEADER;
3467 case EditGame: /*?*/
3468 case EditPosition: /*?*/
3469 /* Should above feature work in these modes too? */
3470 /* For now it doesn't */
3471 ics_getting_history = H_GOT_UNWANTED_HEADER;
3474 ics_getting_history = H_GOT_UNWANTED_HEADER;
3479 /* Is this the right one? */
3480 if (gameInfo.white && gameInfo.black &&
3481 strcmp(gameInfo.white, star_match[0]) == 0 &&
3482 strcmp(gameInfo.black, star_match[2]) == 0) {
3484 ics_getting_history = H_GOT_REQ_HEADER;
3487 case H_GOT_REQ_HEADER:
3488 case H_GOT_UNREQ_HEADER:
3489 case H_GOT_UNWANTED_HEADER:
3490 case H_GETTING_MOVES:
3491 /* Should not happen */
3492 DisplayError(_("Error gathering move list: two headers"), 0);
3493 ics_getting_history = H_FALSE;
3497 /* Save player ratings into gameInfo if needed */
3498 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3499 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3500 (gameInfo.whiteRating == -1 ||
3501 gameInfo.blackRating == -1)) {
3503 gameInfo.whiteRating = string_to_rating(star_match[1]);
3504 gameInfo.blackRating = string_to_rating(star_match[3]);
3505 if (appData.debugMode)
3506 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3507 gameInfo.whiteRating, gameInfo.blackRating);
3512 if (looking_at(buf, &i,
3513 "* * match, initial time: * minute*, increment: * second")) {
3514 /* Header for a move list -- second line */
3515 /* Initial board will follow if this is a wild game */
3516 if (gameInfo.event != NULL) free(gameInfo.event);
3517 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3518 gameInfo.event = StrSave(str);
3519 /* [HGM] we switched variant. Translate boards if needed. */
3520 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3524 if (looking_at(buf, &i, "Move ")) {
3525 /* Beginning of a move list */
3526 switch (ics_getting_history) {
3528 /* Normally should not happen */
3529 /* Maybe user hit reset while we were parsing */
3532 /* Happens if we are ignoring a move list that is not
3533 * the one we just requested. Common if the user
3534 * tries to observe two games without turning off
3537 case H_GETTING_MOVES:
3538 /* Should not happen */
3539 DisplayError(_("Error gathering move list: nested"), 0);
3540 ics_getting_history = H_FALSE;
3542 case H_GOT_REQ_HEADER:
3543 ics_getting_history = H_GETTING_MOVES;
3544 started = STARTED_MOVES;
3546 if (oldi > next_out) {
3547 SendToPlayer(&buf[next_out], oldi - next_out);
3550 case H_GOT_UNREQ_HEADER:
3551 ics_getting_history = H_GETTING_MOVES;
3552 started = STARTED_MOVES_NOHIDE;
3555 case H_GOT_UNWANTED_HEADER:
3556 ics_getting_history = H_FALSE;
3562 if (looking_at(buf, &i, "% ") ||
3563 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3564 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3565 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3566 soughtPending = FALSE;
3570 if(suppressKibitz) next_out = i;
3571 savingComment = FALSE;
3575 case STARTED_MOVES_NOHIDE:
3576 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3577 parse[parse_pos + i - oldi] = NULLCHAR;
3578 ParseGameHistory(parse);
3580 if (appData.zippyPlay && first.initDone) {
3581 FeedMovesToProgram(&first, forwardMostMove);
3582 if (gameMode == IcsPlayingWhite) {
3583 if (WhiteOnMove(forwardMostMove)) {
3584 if (first.sendTime) {
3585 if (first.useColors) {
3586 SendToProgram("black\n", &first);
3588 SendTimeRemaining(&first, TRUE);
3590 if (first.useColors) {
3591 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3593 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3594 first.maybeThinking = TRUE;
3596 if (first.usePlayother) {
3597 if (first.sendTime) {
3598 SendTimeRemaining(&first, TRUE);
3600 SendToProgram("playother\n", &first);
3606 } else if (gameMode == IcsPlayingBlack) {
3607 if (!WhiteOnMove(forwardMostMove)) {
3608 if (first.sendTime) {
3609 if (first.useColors) {
3610 SendToProgram("white\n", &first);
3612 SendTimeRemaining(&first, FALSE);
3614 if (first.useColors) {
3615 SendToProgram("black\n", &first);
3617 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3618 first.maybeThinking = TRUE;
3620 if (first.usePlayother) {
3621 if (first.sendTime) {
3622 SendTimeRemaining(&first, FALSE);
3624 SendToProgram("playother\n", &first);
3633 if (gameMode == IcsObserving && ics_gamenum == -1) {
3634 /* Moves came from oldmoves or moves command
3635 while we weren't doing anything else.
3637 currentMove = forwardMostMove;
3638 ClearHighlights();/*!!could figure this out*/
3639 flipView = appData.flipView;
3640 DrawPosition(TRUE, boards[currentMove]);
3641 DisplayBothClocks();
3642 snprintf(str, MSG_SIZ, "%s %s %s",
3643 gameInfo.white, _("vs."), gameInfo.black);
3647 /* Moves were history of an active game */
3648 if (gameInfo.resultDetails != NULL) {
3649 free(gameInfo.resultDetails);
3650 gameInfo.resultDetails = NULL;
3653 HistorySet(parseList, backwardMostMove,
3654 forwardMostMove, currentMove-1);
3655 DisplayMove(currentMove - 1);
3656 if (started == STARTED_MOVES) next_out = i;
3657 started = STARTED_NONE;
3658 ics_getting_history = H_FALSE;
3661 case STARTED_OBSERVE:
3662 started = STARTED_NONE;
3663 SendToICS(ics_prefix);
3664 SendToICS("refresh\n");
3670 if(bookHit) { // [HGM] book: simulate book reply
3671 static char bookMove[MSG_SIZ]; // a bit generous?
3673 programStats.nodes = programStats.depth = programStats.time =
3674 programStats.score = programStats.got_only_move = 0;
3675 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3677 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3678 strcat(bookMove, bookHit);
3679 HandleMachineMove(bookMove, &first);
3684 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3685 started == STARTED_HOLDINGS ||
3686 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3687 /* Accumulate characters in move list or board */
3688 parse[parse_pos++] = buf[i];
3691 /* Start of game messages. Mostly we detect start of game
3692 when the first board image arrives. On some versions
3693 of the ICS, though, we need to do a "refresh" after starting
3694 to observe in order to get the current board right away. */
3695 if (looking_at(buf, &i, "Adding game * to observation list")) {
3696 started = STARTED_OBSERVE;
3700 /* Handle auto-observe */
3701 if (appData.autoObserve &&
3702 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3703 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3705 /* Choose the player that was highlighted, if any. */
3706 if (star_match[0][0] == '\033' ||
3707 star_match[1][0] != '\033') {
3708 player = star_match[0];
3710 player = star_match[2];
3712 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3713 ics_prefix, StripHighlightAndTitle(player));
3716 /* Save ratings from notify string */
3717 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3718 player1Rating = string_to_rating(star_match[1]);
3719 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3720 player2Rating = string_to_rating(star_match[3]);
3722 if (appData.debugMode)
3724 "Ratings from 'Game notification:' %s %d, %s %d\n",
3725 player1Name, player1Rating,
3726 player2Name, player2Rating);
3731 /* Deal with automatic examine mode after a game,
3732 and with IcsObserving -> IcsExamining transition */
3733 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3734 looking_at(buf, &i, "has made you an examiner of game *")) {
3736 int gamenum = atoi(star_match[0]);
3737 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3738 gamenum == ics_gamenum) {
3739 /* We were already playing or observing this game;
3740 no need to refetch history */
3741 gameMode = IcsExamining;
3743 pauseExamForwardMostMove = forwardMostMove;
3744 } else if (currentMove < forwardMostMove) {
3745 ForwardInner(forwardMostMove);
3748 /* I don't think this case really can happen */
3749 SendToICS(ics_prefix);
3750 SendToICS("refresh\n");
3755 /* Error messages */
3756 // if (ics_user_moved) {
3757 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3758 if (looking_at(buf, &i, "Illegal move") ||
3759 looking_at(buf, &i, "Not a legal move") ||
3760 looking_at(buf, &i, "Your king is in check") ||
3761 looking_at(buf, &i, "It isn't your turn") ||
3762 looking_at(buf, &i, "It is not your move")) {
3764 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3765 currentMove = forwardMostMove-1;
3766 DisplayMove(currentMove - 1); /* before DMError */
3767 DrawPosition(FALSE, boards[currentMove]);
3768 SwitchClocks(forwardMostMove-1); // [HGM] race
3769 DisplayBothClocks();
3771 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3777 if (looking_at(buf, &i, "still have time") ||
3778 looking_at(buf, &i, "not out of time") ||
3779 looking_at(buf, &i, "either player is out of time") ||
3780 looking_at(buf, &i, "has timeseal; checking")) {
3781 /* We must have called his flag a little too soon */
3782 whiteFlag = blackFlag = FALSE;
3786 if (looking_at(buf, &i, "added * seconds to") ||
3787 looking_at(buf, &i, "seconds were added to")) {
3788 /* Update the clocks */
3789 SendToICS(ics_prefix);
3790 SendToICS("refresh\n");
3794 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3795 ics_clock_paused = TRUE;
3800 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3801 ics_clock_paused = FALSE;
3806 /* Grab player ratings from the Creating: message.
3807 Note we have to check for the special case when
3808 the ICS inserts things like [white] or [black]. */
3809 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3810 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3812 0 player 1 name (not necessarily white)
3814 2 empty, white, or black (IGNORED)
3815 3 player 2 name (not necessarily black)
3818 The names/ratings are sorted out when the game
3819 actually starts (below).
3821 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3822 player1Rating = string_to_rating(star_match[1]);
3823 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3824 player2Rating = string_to_rating(star_match[4]);
3826 if (appData.debugMode)
3828 "Ratings from 'Creating:' %s %d, %s %d\n",
3829 player1Name, player1Rating,
3830 player2Name, player2Rating);
3835 /* Improved generic start/end-of-game messages */
3836 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3837 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3838 /* If tkind == 0: */
3839 /* star_match[0] is the game number */
3840 /* [1] is the white player's name */
3841 /* [2] is the black player's name */
3842 /* For end-of-game: */
3843 /* [3] is the reason for the game end */
3844 /* [4] is a PGN end game-token, preceded by " " */
3845 /* For start-of-game: */
3846 /* [3] begins with "Creating" or "Continuing" */
3847 /* [4] is " *" or empty (don't care). */
3848 int gamenum = atoi(star_match[0]);
3849 char *whitename, *blackname, *why, *endtoken;
3850 ChessMove endtype = EndOfFile;
3853 whitename = star_match[1];
3854 blackname = star_match[2];
3855 why = star_match[3];
3856 endtoken = star_match[4];
3858 whitename = star_match[1];
3859 blackname = star_match[3];
3860 why = star_match[5];
3861 endtoken = star_match[6];
3864 /* Game start messages */
3865 if (strncmp(why, "Creating ", 9) == 0 ||
3866 strncmp(why, "Continuing ", 11) == 0) {
3867 gs_gamenum = gamenum;
3868 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3869 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3870 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3872 if (appData.zippyPlay) {
3873 ZippyGameStart(whitename, blackname);
3876 partnerBoardValid = FALSE; // [HGM] bughouse
3880 /* Game end messages */
3881 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3882 ics_gamenum != gamenum) {
3885 while (endtoken[0] == ' ') endtoken++;
3886 switch (endtoken[0]) {
3889 endtype = GameUnfinished;
3892 endtype = BlackWins;
3895 if (endtoken[1] == '/')
3896 endtype = GameIsDrawn;
3898 endtype = WhiteWins;
3901 GameEnds(endtype, why, GE_ICS);
3903 if (appData.zippyPlay && first.initDone) {
3904 ZippyGameEnd(endtype, why);
3905 if (first.pr == NoProc) {
3906 /* Start the next process early so that we'll
3907 be ready for the next challenge */
3908 StartChessProgram(&first);
3910 /* Send "new" early, in case this command takes
3911 a long time to finish, so that we'll be ready
3912 for the next challenge. */
3913 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3917 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3921 if (looking_at(buf, &i, "Removing game * from observation") ||
3922 looking_at(buf, &i, "no longer observing game *") ||
3923 looking_at(buf, &i, "Game * (*) has no examiners")) {
3924 if (gameMode == IcsObserving &&
3925 atoi(star_match[0]) == ics_gamenum)
3927 /* icsEngineAnalyze */
3928 if (appData.icsEngineAnalyze) {
3935 ics_user_moved = FALSE;
3940 if (looking_at(buf, &i, "no longer examining game *")) {
3941 if (gameMode == IcsExamining &&
3942 atoi(star_match[0]) == ics_gamenum)
3946 ics_user_moved = FALSE;
3951 /* Advance leftover_start past any newlines we find,
3952 so only partial lines can get reparsed */
3953 if (looking_at(buf, &i, "\n")) {
3954 prevColor = curColor;
3955 if (curColor != ColorNormal) {
3956 if (oldi > next_out) {
3957 SendToPlayer(&buf[next_out], oldi - next_out);
3960 Colorize(ColorNormal, FALSE);
3961 curColor = ColorNormal;
3963 if (started == STARTED_BOARD) {
3964 started = STARTED_NONE;
3965 parse[parse_pos] = NULLCHAR;
3966 ParseBoard12(parse);
3969 /* Send premove here */
3970 if (appData.premove) {
3972 if (currentMove == 0 &&
3973 gameMode == IcsPlayingWhite &&
3974 appData.premoveWhite) {
3975 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3976 if (appData.debugMode)
3977 fprintf(debugFP, "Sending premove:\n");
3979 } else if (currentMove == 1 &&
3980 gameMode == IcsPlayingBlack &&
3981 appData.premoveBlack) {
3982 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3983 if (appData.debugMode)
3984 fprintf(debugFP, "Sending premove:\n");
3986 } else if (gotPremove) {
3988 ClearPremoveHighlights();
3989 if (appData.debugMode)
3990 fprintf(debugFP, "Sending premove:\n");
3991 UserMoveEvent(premoveFromX, premoveFromY,
3992 premoveToX, premoveToY,
3997 /* Usually suppress following prompt */
3998 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3999 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4000 if (looking_at(buf, &i, "*% ")) {
4001 savingComment = FALSE;
4006 } else if (started == STARTED_HOLDINGS) {
4008 char new_piece[MSG_SIZ];
4009 started = STARTED_NONE;
4010 parse[parse_pos] = NULLCHAR;
4011 if (appData.debugMode)
4012 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4013 parse, currentMove);
4014 if (sscanf(parse, " game %d", &gamenum) == 1) {
4015 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4016 if (gameInfo.variant == VariantNormal) {
4017 /* [HGM] We seem to switch variant during a game!
4018 * Presumably no holdings were displayed, so we have
4019 * to move the position two files to the right to
4020 * create room for them!
4022 VariantClass newVariant;
4023 switch(gameInfo.boardWidth) { // base guess on board width
4024 case 9: newVariant = VariantShogi; break;
4025 case 10: newVariant = VariantGreat; break;
4026 default: newVariant = VariantCrazyhouse; break;
4028 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4029 /* Get a move list just to see the header, which
4030 will tell us whether this is really bug or zh */
4031 if (ics_getting_history == H_FALSE) {
4032 ics_getting_history = H_REQUESTED;
4033 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037 new_piece[0] = NULLCHAR;
4038 sscanf(parse, "game %d white [%s black [%s <- %s",
4039 &gamenum, white_holding, black_holding,
4041 white_holding[strlen(white_holding)-1] = NULLCHAR;
4042 black_holding[strlen(black_holding)-1] = NULLCHAR;
4043 /* [HGM] copy holdings to board holdings area */
4044 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4045 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4046 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4048 if (appData.zippyPlay && first.initDone) {
4049 ZippyHoldings(white_holding, black_holding,
4053 if (tinyLayout || smallLayout) {
4054 char wh[16], bh[16];
4055 PackHolding(wh, white_holding);
4056 PackHolding(bh, black_holding);
4057 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4058 gameInfo.white, gameInfo.black);
4060 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4061 gameInfo.white, white_holding, _("vs."),
4062 gameInfo.black, black_holding);
4064 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4065 DrawPosition(FALSE, boards[currentMove]);
4067 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4068 sscanf(parse, "game %d white [%s black [%s <- %s",
4069 &gamenum, white_holding, black_holding,
4071 white_holding[strlen(white_holding)-1] = NULLCHAR;
4072 black_holding[strlen(black_holding)-1] = NULLCHAR;
4073 /* [HGM] copy holdings to partner-board holdings area */
4074 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4075 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4076 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4077 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4078 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4081 /* Suppress following prompt */
4082 if (looking_at(buf, &i, "*% ")) {
4083 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4084 savingComment = FALSE;
4092 i++; /* skip unparsed character and loop back */
4095 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4096 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4097 // SendToPlayer(&buf[next_out], i - next_out);
4098 started != STARTED_HOLDINGS && leftover_start > next_out) {
4099 SendToPlayer(&buf[next_out], leftover_start - next_out);
4103 leftover_len = buf_len - leftover_start;
4104 /* if buffer ends with something we couldn't parse,
4105 reparse it after appending the next read */
4107 } else if (count == 0) {
4108 RemoveInputSource(isr);
4109 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4111 DisplayFatalError(_("Error reading from ICS"), error, 1);
4116 /* Board style 12 looks like this:
4118 <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
4120 * The "<12> " is stripped before it gets to this routine. The two
4121 * trailing 0's (flip state and clock ticking) are later addition, and
4122 * some chess servers may not have them, or may have only the first.
4123 * Additional trailing fields may be added in the future.
4126 #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"
4128 #define RELATION_OBSERVING_PLAYED 0
4129 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4130 #define RELATION_PLAYING_MYMOVE 1
4131 #define RELATION_PLAYING_NOTMYMOVE -1
4132 #define RELATION_EXAMINING 2
4133 #define RELATION_ISOLATED_BOARD -3
4134 #define RELATION_STARTING_POSITION -4 /* FICS only */
4137 ParseBoard12 (char *string)
4139 GameMode newGameMode;
4140 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4141 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4142 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4143 char to_play, board_chars[200];
4144 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4145 char black[32], white[32];
4147 int prevMove = currentMove;
4150 int fromX, fromY, toX, toY;
4152 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4153 char *bookHit = NULL; // [HGM] book
4154 Boolean weird = FALSE, reqFlag = FALSE;
4156 fromX = fromY = toX = toY = -1;
4160 if (appData.debugMode)
4161 fprintf(debugFP, _("Parsing board: %s\n"), string);
4163 move_str[0] = NULLCHAR;
4164 elapsed_time[0] = NULLCHAR;
4165 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4167 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4168 if(string[i] == ' ') { ranks++; files = 0; }
4170 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4173 for(j = 0; j <i; j++) board_chars[j] = string[j];
4174 board_chars[i] = '\0';
4177 n = sscanf(string, PATTERN, &to_play, &double_push,
4178 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4179 &gamenum, white, black, &relation, &basetime, &increment,
4180 &white_stren, &black_stren, &white_time, &black_time,
4181 &moveNum, str, elapsed_time, move_str, &ics_flip,
4185 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4186 DisplayError(str, 0);
4190 /* Convert the move number to internal form */
4191 moveNum = (moveNum - 1) * 2;
4192 if (to_play == 'B') moveNum++;
4193 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4194 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4200 case RELATION_OBSERVING_PLAYED:
4201 case RELATION_OBSERVING_STATIC:
4202 if (gamenum == -1) {
4203 /* Old ICC buglet */
4204 relation = RELATION_OBSERVING_STATIC;
4206 newGameMode = IcsObserving;
4208 case RELATION_PLAYING_MYMOVE:
4209 case RELATION_PLAYING_NOTMYMOVE:
4211 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4212 IcsPlayingWhite : IcsPlayingBlack;
4214 case RELATION_EXAMINING:
4215 newGameMode = IcsExamining;
4217 case RELATION_ISOLATED_BOARD:
4219 /* Just display this board. If user was doing something else,
4220 we will forget about it until the next board comes. */
4221 newGameMode = IcsIdle;
4223 case RELATION_STARTING_POSITION:
4224 newGameMode = gameMode;
4228 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4229 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4230 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4232 for (k = 0; k < ranks; k++) {
4233 for (j = 0; j < files; j++)
4234 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4235 if(gameInfo.holdingsWidth > 1) {
4236 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4237 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4240 CopyBoard(partnerBoard, board);
4241 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4242 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4243 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4244 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4245 if(toSqr = strchr(str, '-')) {
4246 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4247 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4248 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4249 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4250 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4251 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4252 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4253 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4254 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4255 DisplayMessage(partnerStatus, "");
4256 partnerBoardValid = TRUE;
4260 /* Modify behavior for initial board display on move listing
4263 switch (ics_getting_history) {
4267 case H_GOT_REQ_HEADER:
4268 case H_GOT_UNREQ_HEADER:
4269 /* This is the initial position of the current game */
4270 gamenum = ics_gamenum;
4271 moveNum = 0; /* old ICS bug workaround */
4272 if (to_play == 'B') {
4273 startedFromSetupPosition = TRUE;
4274 blackPlaysFirst = TRUE;
4276 if (forwardMostMove == 0) forwardMostMove = 1;
4277 if (backwardMostMove == 0) backwardMostMove = 1;
4278 if (currentMove == 0) currentMove = 1;
4280 newGameMode = gameMode;
4281 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4283 case H_GOT_UNWANTED_HEADER:
4284 /* This is an initial board that we don't want */
4286 case H_GETTING_MOVES:
4287 /* Should not happen */
4288 DisplayError(_("Error gathering move list: extra board"), 0);
4289 ics_getting_history = H_FALSE;
4293 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4294 weird && (int)gameInfo.variant < (int)VariantShogi) {
4295 /* [HGM] We seem to have switched variant unexpectedly
4296 * Try to guess new variant from board size
4298 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4299 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4300 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4301 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4302 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4303 if(!weird) newVariant = VariantNormal;
4304 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4305 /* Get a move list just to see the header, which
4306 will tell us whether this is really bug or zh */
4307 if (ics_getting_history == H_FALSE) {
4308 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4309 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4314 /* Take action if this is the first board of a new game, or of a
4315 different game than is currently being displayed. */
4316 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4317 relation == RELATION_ISOLATED_BOARD) {
4319 /* Forget the old game and get the history (if any) of the new one */
4320 if (gameMode != BeginningOfGame) {
4324 if (appData.autoRaiseBoard) BoardToTop();
4326 if (gamenum == -1) {
4327 newGameMode = IcsIdle;
4328 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4329 appData.getMoveList && !reqFlag) {
4330 /* Need to get game history */
4331 ics_getting_history = H_REQUESTED;
4332 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4336 /* Initially flip the board to have black on the bottom if playing
4337 black or if the ICS flip flag is set, but let the user change
4338 it with the Flip View button. */
4339 flipView = appData.autoFlipView ?
4340 (newGameMode == IcsPlayingBlack) || ics_flip :
4343 /* Done with values from previous mode; copy in new ones */
4344 gameMode = newGameMode;
4346 ics_gamenum = gamenum;
4347 if (gamenum == gs_gamenum) {
4348 int klen = strlen(gs_kind);
4349 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4350 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4351 gameInfo.event = StrSave(str);
4353 gameInfo.event = StrSave("ICS game");
4355 gameInfo.site = StrSave(appData.icsHost);
4356 gameInfo.date = PGNDate();
4357 gameInfo.round = StrSave("-");
4358 gameInfo.white = StrSave(white);
4359 gameInfo.black = StrSave(black);
4360 timeControl = basetime * 60 * 1000;
4362 timeIncrement = increment * 1000;
4363 movesPerSession = 0;
4364 gameInfo.timeControl = TimeControlTagValue();
4365 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4366 if (appData.debugMode) {
4367 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4368 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4369 setbuf(debugFP, NULL);
4372 gameInfo.outOfBook = NULL;
4374 /* Do we have the ratings? */
4375 if (strcmp(player1Name, white) == 0 &&
4376 strcmp(player2Name, black) == 0) {
4377 if (appData.debugMode)
4378 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4379 player1Rating, player2Rating);
4380 gameInfo.whiteRating = player1Rating;
4381 gameInfo.blackRating = player2Rating;
4382 } else if (strcmp(player2Name, white) == 0 &&
4383 strcmp(player1Name, black) == 0) {
4384 if (appData.debugMode)
4385 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4386 player2Rating, player1Rating);
4387 gameInfo.whiteRating = player2Rating;
4388 gameInfo.blackRating = player1Rating;
4390 player1Name[0] = player2Name[0] = NULLCHAR;
4392 /* Silence shouts if requested */
4393 if (appData.quietPlay &&
4394 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4395 SendToICS(ics_prefix);
4396 SendToICS("set shout 0\n");
4400 /* Deal with midgame name changes */
4402 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4403 if (gameInfo.white) free(gameInfo.white);
4404 gameInfo.white = StrSave(white);
4406 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4407 if (gameInfo.black) free(gameInfo.black);
4408 gameInfo.black = StrSave(black);
4412 /* Throw away game result if anything actually changes in examine mode */
4413 if (gameMode == IcsExamining && !newGame) {
4414 gameInfo.result = GameUnfinished;
4415 if (gameInfo.resultDetails != NULL) {
4416 free(gameInfo.resultDetails);
4417 gameInfo.resultDetails = NULL;
4421 /* In pausing && IcsExamining mode, we ignore boards coming
4422 in if they are in a different variation than we are. */
4423 if (pauseExamInvalid) return;
4424 if (pausing && gameMode == IcsExamining) {
4425 if (moveNum <= pauseExamForwardMostMove) {
4426 pauseExamInvalid = TRUE;
4427 forwardMostMove = pauseExamForwardMostMove;
4432 if (appData.debugMode) {
4433 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4435 /* Parse the board */
4436 for (k = 0; k < ranks; k++) {
4437 for (j = 0; j < files; j++)
4438 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4439 if(gameInfo.holdingsWidth > 1) {
4440 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4441 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4444 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4445 board[5][BOARD_RGHT+1] = WhiteAngel;
4446 board[6][BOARD_RGHT+1] = WhiteMarshall;
4447 board[1][0] = BlackMarshall;
4448 board[2][0] = BlackAngel;
4449 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4451 CopyBoard(boards[moveNum], board);
4452 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4454 startedFromSetupPosition =
4455 !CompareBoards(board, initialPosition);
4456 if(startedFromSetupPosition)
4457 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4460 /* [HGM] Set castling rights. Take the outermost Rooks,
4461 to make it also work for FRC opening positions. Note that board12
4462 is really defective for later FRC positions, as it has no way to
4463 indicate which Rook can castle if they are on the same side of King.
4464 For the initial position we grant rights to the outermost Rooks,
4465 and remember thos rights, and we then copy them on positions
4466 later in an FRC game. This means WB might not recognize castlings with
4467 Rooks that have moved back to their original position as illegal,
4468 but in ICS mode that is not its job anyway.
4470 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4471 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4473 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474 if(board[0][i] == WhiteRook) j = i;
4475 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477 if(board[0][i] == WhiteRook) j = i;
4478 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4480 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4481 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4482 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4483 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4484 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4486 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4487 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4488 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4490 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4491 if(board[BOARD_HEIGHT-1][k] == bKing)
4492 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4493 if(gameInfo.variant == VariantTwoKings) {
4494 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4495 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4496 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4499 r = boards[moveNum][CASTLING][0] = initialRights[0];
4500 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4501 r = boards[moveNum][CASTLING][1] = initialRights[1];
4502 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4503 r = boards[moveNum][CASTLING][3] = initialRights[3];
4504 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4505 r = boards[moveNum][CASTLING][4] = initialRights[4];
4506 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4507 /* wildcastle kludge: always assume King has rights */
4508 r = boards[moveNum][CASTLING][2] = initialRights[2];
4509 r = boards[moveNum][CASTLING][5] = initialRights[5];
4511 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4512 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4515 if (ics_getting_history == H_GOT_REQ_HEADER ||
4516 ics_getting_history == H_GOT_UNREQ_HEADER) {
4517 /* This was an initial position from a move list, not
4518 the current position */
4522 /* Update currentMove and known move number limits */
4523 newMove = newGame || moveNum > forwardMostMove;
4526 forwardMostMove = backwardMostMove = currentMove = moveNum;
4527 if (gameMode == IcsExamining && moveNum == 0) {
4528 /* Workaround for ICS limitation: we are not told the wild
4529 type when starting to examine a game. But if we ask for
4530 the move list, the move list header will tell us */
4531 ics_getting_history = H_REQUESTED;
4532 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4535 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4536 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4538 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4539 /* [HGM] applied this also to an engine that is silently watching */
4540 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4541 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4542 gameInfo.variant == currentlyInitializedVariant) {
4543 takeback = forwardMostMove - moveNum;
4544 for (i = 0; i < takeback; i++) {
4545 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4546 SendToProgram("undo\n", &first);
4551 forwardMostMove = moveNum;
4552 if (!pausing || currentMove > forwardMostMove)
4553 currentMove = forwardMostMove;
4555 /* New part of history that is not contiguous with old part */
4556 if (pausing && gameMode == IcsExamining) {
4557 pauseExamInvalid = TRUE;
4558 forwardMostMove = pauseExamForwardMostMove;
4561 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4563 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4564 // [HGM] when we will receive the move list we now request, it will be
4565 // fed to the engine from the first move on. So if the engine is not
4566 // in the initial position now, bring it there.
4567 InitChessProgram(&first, 0);
4570 ics_getting_history = H_REQUESTED;
4571 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4574 forwardMostMove = backwardMostMove = currentMove = moveNum;
4577 /* Update the clocks */
4578 if (strchr(elapsed_time, '.')) {
4580 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4581 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4583 /* Time is in seconds */
4584 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4585 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4590 if (appData.zippyPlay && newGame &&
4591 gameMode != IcsObserving && gameMode != IcsIdle &&
4592 gameMode != IcsExamining)
4593 ZippyFirstBoard(moveNum, basetime, increment);
4596 /* Put the move on the move list, first converting
4597 to canonical algebraic form. */
4599 if (appData.debugMode) {
4600 if (appData.debugMode) { int f = forwardMostMove;
4601 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4602 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4603 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4605 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4606 fprintf(debugFP, "moveNum = %d\n", moveNum);
4607 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4608 setbuf(debugFP, NULL);
4610 if (moveNum <= backwardMostMove) {
4611 /* We don't know what the board looked like before
4613 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4614 strcat(parseList[moveNum - 1], " ");
4615 strcat(parseList[moveNum - 1], elapsed_time);
4616 moveList[moveNum - 1][0] = NULLCHAR;
4617 } else if (strcmp(move_str, "none") == 0) {
4618 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4619 /* Again, we don't know what the board looked like;
4620 this is really the start of the game. */
4621 parseList[moveNum - 1][0] = NULLCHAR;
4622 moveList[moveNum - 1][0] = NULLCHAR;
4623 backwardMostMove = moveNum;
4624 startedFromSetupPosition = TRUE;
4625 fromX = fromY = toX = toY = -1;
4627 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4628 // So we parse the long-algebraic move string in stead of the SAN move
4629 int valid; char buf[MSG_SIZ], *prom;
4631 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4632 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4633 // str looks something like "Q/a1-a2"; kill the slash
4635 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4636 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4637 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4638 strcat(buf, prom); // long move lacks promo specification!
4639 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4640 if(appData.debugMode)
4641 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4642 safeStrCpy(move_str, buf, MSG_SIZ);
4644 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4645 &fromX, &fromY, &toX, &toY, &promoChar)
4646 || ParseOneMove(buf, moveNum - 1, &moveType,
4647 &fromX, &fromY, &toX, &toY, &promoChar);
4648 // end of long SAN patch
4650 (void) CoordsToAlgebraic(boards[moveNum - 1],
4651 PosFlags(moveNum - 1),
4652 fromY, fromX, toY, toX, promoChar,
4653 parseList[moveNum-1]);
4654 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4660 if(gameInfo.variant != VariantShogi)
4661 strcat(parseList[moveNum - 1], "+");
4664 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4665 strcat(parseList[moveNum - 1], "#");
4668 strcat(parseList[moveNum - 1], " ");
4669 strcat(parseList[moveNum - 1], elapsed_time);
4670 /* currentMoveString is set as a side-effect of ParseOneMove */
4671 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4672 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4673 strcat(moveList[moveNum - 1], "\n");
4675 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4676 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4677 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4678 ChessSquare old, new = boards[moveNum][k][j];
4679 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4680 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4681 if(old == new) continue;
4682 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4683 else if(new == WhiteWazir || new == BlackWazir) {
4684 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4685 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4686 else boards[moveNum][k][j] = old; // preserve type of Gold
4687 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4688 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4691 /* Move from ICS was illegal!? Punt. */
4692 if (appData.debugMode) {
4693 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4694 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4696 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4697 strcat(parseList[moveNum - 1], " ");
4698 strcat(parseList[moveNum - 1], elapsed_time);
4699 moveList[moveNum - 1][0] = NULLCHAR;
4700 fromX = fromY = toX = toY = -1;
4703 if (appData.debugMode) {
4704 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4705 setbuf(debugFP, NULL);
4709 /* Send move to chess program (BEFORE animating it). */
4710 if (appData.zippyPlay && !newGame && newMove &&
4711 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4713 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4714 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4715 if (moveList[moveNum - 1][0] == NULLCHAR) {
4716 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4718 DisplayError(str, 0);
4720 if (first.sendTime) {
4721 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4723 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4724 if (firstMove && !bookHit) {
4726 if (first.useColors) {
4727 SendToProgram(gameMode == IcsPlayingWhite ?
4729 "black\ngo\n", &first);
4731 SendToProgram("go\n", &first);
4733 first.maybeThinking = TRUE;
4736 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4737 if (moveList[moveNum - 1][0] == NULLCHAR) {
4738 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4739 DisplayError(str, 0);
4741 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4742 SendMoveToProgram(moveNum - 1, &first);
4749 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4750 /* If move comes from a remote source, animate it. If it
4751 isn't remote, it will have already been animated. */
4752 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4753 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4755 if (!pausing && appData.highlightLastMove) {
4756 SetHighlights(fromX, fromY, toX, toY);
4760 /* Start the clocks */
4761 whiteFlag = blackFlag = FALSE;
4762 appData.clockMode = !(basetime == 0 && increment == 0);
4764 ics_clock_paused = TRUE;
4766 } else if (ticking == 1) {
4767 ics_clock_paused = FALSE;
4769 if (gameMode == IcsIdle ||
4770 relation == RELATION_OBSERVING_STATIC ||
4771 relation == RELATION_EXAMINING ||
4773 DisplayBothClocks();
4777 /* Display opponents and material strengths */
4778 if (gameInfo.variant != VariantBughouse &&
4779 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4780 if (tinyLayout || smallLayout) {
4781 if(gameInfo.variant == VariantNormal)
4782 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4783 gameInfo.white, white_stren, gameInfo.black, black_stren,
4784 basetime, increment);
4786 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4787 gameInfo.white, white_stren, gameInfo.black, black_stren,
4788 basetime, increment, (int) gameInfo.variant);
4790 if(gameInfo.variant == VariantNormal)
4791 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4792 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4793 basetime, increment);
4795 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4796 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4797 basetime, increment, VariantName(gameInfo.variant));
4800 if (appData.debugMode) {
4801 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4806 /* Display the board */
4807 if (!pausing && !appData.noGUI) {
4809 if (appData.premove)
4811 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4812 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4813 ClearPremoveHighlights();
4815 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4816 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4817 DrawPosition(j, boards[currentMove]);
4819 DisplayMove(moveNum - 1);
4820 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4821 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4822 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4823 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4827 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4829 if(bookHit) { // [HGM] book: simulate book reply
4830 static char bookMove[MSG_SIZ]; // a bit generous?
4832 programStats.nodes = programStats.depth = programStats.time =
4833 programStats.score = programStats.got_only_move = 0;
4834 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4836 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4837 strcat(bookMove, bookHit);
4838 HandleMachineMove(bookMove, &first);
4847 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4848 ics_getting_history = H_REQUESTED;
4849 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4855 AnalysisPeriodicEvent (int force)
4857 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4858 && !force) || !appData.periodicUpdates)
4861 /* Send . command to Crafty to collect stats */
4862 SendToProgram(".\n", &first);
4864 /* Don't send another until we get a response (this makes
4865 us stop sending to old Crafty's which don't understand
4866 the "." command (sending illegal cmds resets node count & time,
4867 which looks bad)) */
4868 programStats.ok_to_send = 0;
4872 ics_update_width (int new_width)
4874 ics_printf("set width %d\n", new_width);
4878 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4882 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4883 // null move in variant where engine does not understand it (for analysis purposes)
4884 SendBoard(cps, moveNum + 1); // send position after move in stead.
4887 if (cps->useUsermove) {
4888 SendToProgram("usermove ", cps);
4892 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4893 int len = space - parseList[moveNum];
4894 memcpy(buf, parseList[moveNum], len);
4896 buf[len] = NULLCHAR;
4898 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4900 SendToProgram(buf, cps);
4902 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4903 AlphaRank(moveList[moveNum], 4);
4904 SendToProgram(moveList[moveNum], cps);
4905 AlphaRank(moveList[moveNum], 4); // and back
4907 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4908 * the engine. It would be nice to have a better way to identify castle
4910 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4911 && cps->useOOCastle) {
4912 int fromX = moveList[moveNum][0] - AAA;
4913 int fromY = moveList[moveNum][1] - ONE;
4914 int toX = moveList[moveNum][2] - AAA;
4915 int toY = moveList[moveNum][3] - ONE;
4916 if((boards[moveNum][fromY][fromX] == WhiteKing
4917 && boards[moveNum][toY][toX] == WhiteRook)
4918 || (boards[moveNum][fromY][fromX] == BlackKing
4919 && boards[moveNum][toY][toX] == BlackRook)) {
4920 if(toX > fromX) SendToProgram("O-O\n", cps);
4921 else SendToProgram("O-O-O\n", cps);
4923 else SendToProgram(moveList[moveNum], cps);
4925 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4926 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4927 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4928 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4929 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4931 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4932 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4933 SendToProgram(buf, cps);
4935 else SendToProgram(moveList[moveNum], cps);
4936 /* End of additions by Tord */
4939 /* [HGM] setting up the opening has brought engine in force mode! */
4940 /* Send 'go' if we are in a mode where machine should play. */
4941 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4942 (gameMode == TwoMachinesPlay ||
4944 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4946 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4947 SendToProgram("go\n", cps);
4948 if (appData.debugMode) {
4949 fprintf(debugFP, "(extra)\n");
4952 setboardSpoiledMachineBlack = 0;
4956 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4958 char user_move[MSG_SIZ];
4961 if(gameInfo.variant == VariantSChess && promoChar) {
4962 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4963 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4964 } else suffix[0] = NULLCHAR;
4968 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4969 (int)moveType, fromX, fromY, toX, toY);
4970 DisplayError(user_move + strlen("say "), 0);
4972 case WhiteKingSideCastle:
4973 case BlackKingSideCastle:
4974 case WhiteQueenSideCastleWild:
4975 case BlackQueenSideCastleWild:
4977 case WhiteHSideCastleFR:
4978 case BlackHSideCastleFR:
4980 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4982 case WhiteQueenSideCastle:
4983 case BlackQueenSideCastle:
4984 case WhiteKingSideCastleWild:
4985 case BlackKingSideCastleWild:
4987 case WhiteASideCastleFR:
4988 case BlackASideCastleFR:
4990 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4992 case WhiteNonPromotion:
4993 case BlackNonPromotion:
4994 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4996 case WhitePromotion:
4997 case BlackPromotion:
4998 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4999 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5000 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001 PieceToChar(WhiteFerz));
5002 else if(gameInfo.variant == VariantGreat)
5003 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5004 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005 PieceToChar(WhiteMan));
5007 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5008 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5014 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5015 ToUpper(PieceToChar((ChessSquare) fromX)),
5016 AAA + toX, ONE + toY);
5018 case IllegalMove: /* could be a variant we don't quite understand */
5019 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5021 case WhiteCapturesEnPassant:
5022 case BlackCapturesEnPassant:
5023 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5024 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5027 SendToICS(user_move);
5028 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5029 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5034 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5035 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5036 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5037 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5038 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5041 if(gameMode != IcsExamining) { // is this ever not the case?
5042 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5044 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5045 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5046 } else { // on FICS we must first go to general examine mode
5047 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5049 if(gameInfo.variant != VariantNormal) {
5050 // try figure out wild number, as xboard names are not always valid on ICS
5051 for(i=1; i<=36; i++) {
5052 snprintf(buf, MSG_SIZ, "wild/%d", i);
5053 if(StringToVariant(buf) == gameInfo.variant) break;
5055 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5056 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5057 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5058 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5059 SendToICS(ics_prefix);
5061 if(startedFromSetupPosition || backwardMostMove != 0) {
5062 fen = PositionToFEN(backwardMostMove, NULL);
5063 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5064 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5066 } else { // FICS: everything has to set by separate bsetup commands
5067 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5068 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5070 if(!WhiteOnMove(backwardMostMove)) {
5071 SendToICS("bsetup tomove black\n");
5073 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5074 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5076 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5077 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5079 i = boards[backwardMostMove][EP_STATUS];
5080 if(i >= 0) { // set e.p.
5081 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5087 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5088 SendToICS("bsetup done\n"); // switch to normal examining.
5090 for(i = backwardMostMove; i<last; i++) {
5092 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5095 SendToICS(ics_prefix);
5096 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5100 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5102 if (rf == DROP_RANK) {
5103 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5104 sprintf(move, "%c@%c%c\n",
5105 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5107 if (promoChar == 'x' || promoChar == NULLCHAR) {
5108 sprintf(move, "%c%c%c%c\n",
5109 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5111 sprintf(move, "%c%c%c%c%c\n",
5112 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5118 ProcessICSInitScript (FILE *f)
5122 while (fgets(buf, MSG_SIZ, f)) {
5123 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5130 static int lastX, lastY, selectFlag, dragging;
5135 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5136 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5137 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5138 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5139 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5140 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5143 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5144 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5145 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5146 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5147 if(!step) step = -1;
5148 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5149 appData.testLegality && (promoSweep == king ||
5150 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5151 ChangeDragPiece(promoSweep);
5155 PromoScroll (int x, int y)
5159 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5160 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5161 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5162 if(!step) return FALSE;
5163 lastX = x; lastY = y;
5164 if((promoSweep < BlackPawn) == flipView) step = -step;
5165 if(step > 0) selectFlag = 1;
5166 if(!selectFlag) Sweep(step);
5171 NextPiece (int step)
5173 ChessSquare piece = boards[currentMove][toY][toX];
5176 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5177 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5178 if(!step) step = -1;
5179 } while(PieceToChar(pieceSweep) == '.');
5180 boards[currentMove][toY][toX] = pieceSweep;
5181 DrawPosition(FALSE, boards[currentMove]);
5182 boards[currentMove][toY][toX] = piece;
5184 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5186 AlphaRank (char *move, int n)
5188 // char *p = move, c; int x, y;
5190 if (appData.debugMode) {
5191 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5195 move[2]>='0' && move[2]<='9' &&
5196 move[3]>='a' && move[3]<='x' ) {
5198 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5199 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201 if(move[0]>='0' && move[0]<='9' &&
5202 move[1]>='a' && move[1]<='x' &&
5203 move[2]>='0' && move[2]<='9' &&
5204 move[3]>='a' && move[3]<='x' ) {
5205 /* input move, Shogi -> normal */
5206 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5207 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5208 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5209 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5212 move[3]>='0' && move[3]<='9' &&
5213 move[2]>='a' && move[2]<='x' ) {
5215 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5216 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5219 move[0]>='a' && move[0]<='x' &&
5220 move[3]>='0' && move[3]<='9' &&
5221 move[2]>='a' && move[2]<='x' ) {
5222 /* output move, normal -> Shogi */
5223 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5224 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5225 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5226 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5227 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5229 if (appData.debugMode) {
5230 fprintf(debugFP, " out = '%s'\n", move);
5234 char yy_textstr[8000];
5236 /* Parser for moves from gnuchess, ICS, or user typein box */
5238 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5240 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5242 switch (*moveType) {
5243 case WhitePromotion:
5244 case BlackPromotion:
5245 case WhiteNonPromotion:
5246 case BlackNonPromotion:
5248 case WhiteCapturesEnPassant:
5249 case BlackCapturesEnPassant:
5250 case WhiteKingSideCastle:
5251 case WhiteQueenSideCastle:
5252 case BlackKingSideCastle:
5253 case BlackQueenSideCastle:
5254 case WhiteKingSideCastleWild:
5255 case WhiteQueenSideCastleWild:
5256 case BlackKingSideCastleWild:
5257 case BlackQueenSideCastleWild:
5258 /* Code added by Tord: */
5259 case WhiteHSideCastleFR:
5260 case WhiteASideCastleFR:
5261 case BlackHSideCastleFR:
5262 case BlackASideCastleFR:
5263 /* End of code added by Tord */
5264 case IllegalMove: /* bug or odd chess variant */
5265 *fromX = currentMoveString[0] - AAA;
5266 *fromY = currentMoveString[1] - ONE;
5267 *toX = currentMoveString[2] - AAA;
5268 *toY = currentMoveString[3] - ONE;
5269 *promoChar = currentMoveString[4];
5270 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5271 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5272 if (appData.debugMode) {
5273 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5275 *fromX = *fromY = *toX = *toY = 0;
5278 if (appData.testLegality) {
5279 return (*moveType != IllegalMove);
5281 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5282 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5287 *fromX = *moveType == WhiteDrop ?
5288 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5289 (int) CharToPiece(ToLower(currentMoveString[0]));
5291 *toX = currentMoveString[2] - AAA;
5292 *toY = currentMoveString[3] - ONE;
5293 *promoChar = NULLCHAR;
5297 case ImpossibleMove:
5307 if (appData.debugMode) {
5308 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5311 *fromX = *fromY = *toX = *toY = 0;
5312 *promoChar = NULLCHAR;
5317 Boolean pushed = FALSE;
5318 char *lastParseAttempt;
5321 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5322 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5323 int fromX, fromY, toX, toY; char promoChar;
5328 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5329 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5332 endPV = forwardMostMove;
5334 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5335 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5336 lastParseAttempt = pv;
5337 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5338 if(!valid && nr == 0 &&
5339 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5340 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5341 // Hande case where played move is different from leading PV move
5342 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5343 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5344 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5345 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5346 endPV += 2; // if position different, keep this
5347 moveList[endPV-1][0] = fromX + AAA;
5348 moveList[endPV-1][1] = fromY + ONE;
5349 moveList[endPV-1][2] = toX + AAA;
5350 moveList[endPV-1][3] = toY + ONE;
5351 parseList[endPV-1][0] = NULLCHAR;
5352 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5355 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5356 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5357 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5358 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5359 valid++; // allow comments in PV
5363 if(endPV+1 > framePtr) break; // no space, truncate
5366 CopyBoard(boards[endPV], boards[endPV-1]);
5367 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5368 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5369 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5370 CoordsToAlgebraic(boards[endPV - 1],
5371 PosFlags(endPV - 1),
5372 fromY, fromX, toY, toX, promoChar,
5373 parseList[endPV - 1]);
5375 if(atEnd == 2) return; // used hidden, for PV conversion
5376 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5377 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380 DrawPosition(TRUE, boards[currentMove]);
5384 MultiPV (ChessProgramState *cps)
5385 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5387 for(i=0; i<cps->nrOptions; i++)
5388 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5394 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5396 int startPV, multi, lineStart, origIndex = index;
5397 char *p, buf2[MSG_SIZ];
5399 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5400 lastX = x; lastY = y;
5401 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5402 lineStart = startPV = index;
5403 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5404 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5406 do{ while(buf[index] && buf[index] != '\n') index++;
5407 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5409 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5410 int n = first.option[multi].value;
5411 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5412 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5413 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5414 first.option[multi].value = n;
5418 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5419 *start = startPV; *end = index-1;
5426 static char buf[10*MSG_SIZ];
5427 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5429 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5430 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5431 for(i = forwardMostMove; i<endPV; i++){
5432 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5433 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5436 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5437 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5443 LoadPV (int x, int y)
5444 { // called on right mouse click to load PV
5445 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5446 lastX = x; lastY = y;
5447 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5454 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5455 if(endPV < 0) return;
5456 if(appData.autoCopyPV) CopyFENToClipboard();
5458 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5459 Boolean saveAnimate = appData.animate;
5461 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5462 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5463 } else storedGames--; // abandon shelved tail of original game
5466 forwardMostMove = currentMove;
5467 currentMove = oldFMM;
5468 appData.animate = FALSE;
5469 ToNrEvent(forwardMostMove);
5470 appData.animate = saveAnimate;
5472 currentMove = forwardMostMove;
5473 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5474 ClearPremoveHighlights();
5475 DrawPosition(TRUE, boards[currentMove]);
5479 MovePV (int x, int y, int h)
5480 { // step through PV based on mouse coordinates (called on mouse move)
5481 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5483 // we must somehow check if right button is still down (might be released off board!)
5484 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5485 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5486 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5488 lastX = x; lastY = y;
5490 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5491 if(endPV < 0) return;
5492 if(y < margin) step = 1; else
5493 if(y > h - margin) step = -1;
5494 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5495 currentMove += step;
5496 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499 DrawPosition(FALSE, boards[currentMove]);
5503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5504 // All positions will have equal probability, but the current method will not provide a unique
5505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5511 int piecesLeft[(int)BlackPawn];
5512 int seed, nrOfShuffles;
5515 GetPositionNumber ()
5516 { // sets global variable seed
5519 seed = appData.defaultFrcPosition;
5520 if(seed < 0) { // randomize based on time for negative FRC position numbers
5521 for(i=0; i<50; i++) seed += random();
5522 seed = random() ^ random() >> 8 ^ random() << 8;
5523 if(seed<0) seed = -seed;
5528 put (Board board, int pieceType, int rank, int n, int shade)
5529 // put the piece on the (n-1)-th empty squares of the given shade
5533 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5534 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5535 board[rank][i] = (ChessSquare) pieceType;
5536 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5538 piecesLeft[pieceType]--;
5547 AddOnePiece (Board board, int pieceType, int rank, int shade)
5548 // calculate where the next piece goes, (any empty square), and put it there
5552 i = seed % squaresLeft[shade];
5553 nrOfShuffles *= squaresLeft[shade];
5554 seed /= squaresLeft[shade];
5555 put(board, pieceType, rank, i, shade);
5559 AddTwoPieces (Board board, int pieceType, int rank)
5560 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5562 int i, n=squaresLeft[ANY], j=n-1, k;
5564 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5565 i = seed % k; // pick one
5568 while(i >= j) i -= j--;
5569 j = n - 1 - j; i += j;
5570 put(board, pieceType, rank, j, ANY);
5571 put(board, pieceType, rank, i, ANY);
5575 SetUpShuffle (Board board, int number)
5579 GetPositionNumber(); nrOfShuffles = 1;
5581 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5582 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5583 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5585 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5587 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5588 p = (int) board[0][i];
5589 if(p < (int) BlackPawn) piecesLeft[p] ++;
5590 board[0][i] = EmptySquare;
5593 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5594 // shuffles restricted to allow normal castling put KRR first
5595 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5596 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5597 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5598 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5599 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5600 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5601 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5602 put(board, WhiteRook, 0, 0, ANY);
5603 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5606 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5607 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5608 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5609 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5610 while(piecesLeft[p] >= 2) {
5611 AddOnePiece(board, p, 0, LITE);
5612 AddOnePiece(board, p, 0, DARK);
5614 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5617 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5618 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5619 // but we leave King and Rooks for last, to possibly obey FRC restriction
5620 if(p == (int)WhiteRook) continue;
5621 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5622 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5625 // now everything is placed, except perhaps King (Unicorn) and Rooks
5627 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5628 // Last King gets castling rights
5629 while(piecesLeft[(int)WhiteUnicorn]) {
5630 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5634 while(piecesLeft[(int)WhiteKing]) {
5635 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5636 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5641 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5642 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5645 // Only Rooks can be left; simply place them all
5646 while(piecesLeft[(int)WhiteRook]) {
5647 i = put(board, WhiteRook, 0, 0, ANY);
5648 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5651 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5653 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5656 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5657 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5660 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5664 SetCharTable (char *table, const char * map)
5665 /* [HGM] moved here from winboard.c because of its general usefulness */
5666 /* Basically a safe strcpy that uses the last character as King */
5668 int result = FALSE; int NrPieces;
5670 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5671 && NrPieces >= 12 && !(NrPieces&1)) {
5672 int i; /* [HGM] Accept even length from 12 to 34 */
5674 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5675 for( i=0; i<NrPieces/2-1; i++ ) {
5677 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5679 table[(int) WhiteKing] = map[NrPieces/2-1];
5680 table[(int) BlackKing] = map[NrPieces-1];
5689 Prelude (Board board)
5690 { // [HGM] superchess: random selection of exo-pieces
5691 int i, j, k; ChessSquare p;
5692 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5694 GetPositionNumber(); // use FRC position number
5696 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5697 SetCharTable(pieceToChar, appData.pieceToCharTable);
5698 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5699 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5702 j = seed%4; seed /= 4;
5703 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5704 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5705 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5706 j = seed%3 + (seed%3 >= j); seed /= 3;
5707 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5708 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5709 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5710 j = seed%3; seed /= 3;
5711 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5712 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5713 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5714 j = seed%2 + (seed%2 >= j); seed /= 2;
5715 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5716 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5717 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5718 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5719 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5720 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5721 put(board, exoPieces[0], 0, 0, ANY);
5722 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5726 InitPosition (int redraw)
5728 ChessSquare (* pieces)[BOARD_FILES];
5729 int i, j, pawnRow, overrule,
5730 oldx = gameInfo.boardWidth,
5731 oldy = gameInfo.boardHeight,
5732 oldh = gameInfo.holdingsWidth;
5735 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5737 /* [AS] Initialize pv info list [HGM] and game status */
5739 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5740 pvInfoList[i].depth = 0;
5741 boards[i][EP_STATUS] = EP_NONE;
5742 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5745 initialRulePlies = 0; /* 50-move counter start */
5747 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5748 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5752 /* [HGM] logic here is completely changed. In stead of full positions */
5753 /* the initialized data only consist of the two backranks. The switch */
5754 /* selects which one we will use, which is than copied to the Board */
5755 /* initialPosition, which for the rest is initialized by Pawns and */
5756 /* empty squares. This initial position is then copied to boards[0], */
5757 /* possibly after shuffling, so that it remains available. */
5759 gameInfo.holdingsWidth = 0; /* default board sizes */
5760 gameInfo.boardWidth = 8;
5761 gameInfo.boardHeight = 8;
5762 gameInfo.holdingsSize = 0;
5763 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5764 for(i=0; i<BOARD_FILES-2; i++)
5765 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5766 initialPosition[EP_STATUS] = EP_NONE;
5767 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5768 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5769 SetCharTable(pieceNickName, appData.pieceNickNames);
5770 else SetCharTable(pieceNickName, "............");
5773 switch (gameInfo.variant) {
5774 case VariantFischeRandom:
5775 shuffleOpenings = TRUE;
5778 case VariantShatranj:
5779 pieces = ShatranjArray;
5780 nrCastlingRights = 0;
5781 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5784 pieces = makrukArray;
5785 nrCastlingRights = 0;
5786 startedFromSetupPosition = TRUE;
5787 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5789 case VariantTwoKings:
5790 pieces = twoKingsArray;
5793 pieces = GrandArray;
5794 nrCastlingRights = 0;
5795 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5796 gameInfo.boardWidth = 10;
5797 gameInfo.boardHeight = 10;
5798 gameInfo.holdingsSize = 7;
5800 case VariantCapaRandom:
5801 shuffleOpenings = TRUE;
5802 case VariantCapablanca:
5803 pieces = CapablancaArray;
5804 gameInfo.boardWidth = 10;
5805 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808 pieces = GothicArray;
5809 gameInfo.boardWidth = 10;
5810 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5813 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5814 gameInfo.holdingsSize = 7;
5817 pieces = JanusArray;
5818 gameInfo.boardWidth = 10;
5819 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5820 nrCastlingRights = 6;
5821 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5822 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5823 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5824 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5825 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5826 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5829 pieces = FalconArray;
5830 gameInfo.boardWidth = 10;
5831 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5833 case VariantXiangqi:
5834 pieces = XiangqiArray;
5835 gameInfo.boardWidth = 9;
5836 gameInfo.boardHeight = 10;
5837 nrCastlingRights = 0;
5838 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5841 pieces = ShogiArray;
5842 gameInfo.boardWidth = 9;
5843 gameInfo.boardHeight = 9;
5844 gameInfo.holdingsSize = 7;
5845 nrCastlingRights = 0;
5846 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5848 case VariantCourier:
5849 pieces = CourierArray;
5850 gameInfo.boardWidth = 12;
5851 nrCastlingRights = 0;
5852 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5854 case VariantKnightmate:
5855 pieces = KnightmateArray;
5856 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5858 case VariantSpartan:
5859 pieces = SpartanArray;
5860 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5863 pieces = fairyArray;
5864 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5867 pieces = GreatArray;
5868 gameInfo.boardWidth = 10;
5869 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5870 gameInfo.holdingsSize = 8;
5874 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5875 gameInfo.holdingsSize = 8;
5876 startedFromSetupPosition = TRUE;
5878 case VariantCrazyhouse:
5879 case VariantBughouse:
5881 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5882 gameInfo.holdingsSize = 5;
5884 case VariantWildCastle:
5886 /* !!?shuffle with kings guaranteed to be on d or e file */
5887 shuffleOpenings = 1;
5889 case VariantNoCastle:
5891 nrCastlingRights = 0;
5892 /* !!?unconstrained back-rank shuffle */
5893 shuffleOpenings = 1;
5898 if(appData.NrFiles >= 0) {
5899 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5900 gameInfo.boardWidth = appData.NrFiles;
5902 if(appData.NrRanks >= 0) {
5903 gameInfo.boardHeight = appData.NrRanks;
5905 if(appData.holdingsSize >= 0) {
5906 i = appData.holdingsSize;
5907 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5908 gameInfo.holdingsSize = i;
5910 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5911 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5912 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5914 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5915 if(pawnRow < 1) pawnRow = 1;
5916 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5918 /* User pieceToChar list overrules defaults */
5919 if(appData.pieceToCharTable != NULL)
5920 SetCharTable(pieceToChar, appData.pieceToCharTable);
5922 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5924 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5925 s = (ChessSquare) 0; /* account holding counts in guard band */
5926 for( i=0; i<BOARD_HEIGHT; i++ )
5927 initialPosition[i][j] = s;
5929 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5930 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5931 initialPosition[pawnRow][j] = WhitePawn;
5932 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5933 if(gameInfo.variant == VariantXiangqi) {
5935 initialPosition[pawnRow][j] =
5936 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5937 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5938 initialPosition[2][j] = WhiteCannon;
5939 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5943 if(gameInfo.variant == VariantGrand) {
5944 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5945 initialPosition[0][j] = WhiteRook;
5946 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5949 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5951 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5954 initialPosition[1][j] = WhiteBishop;
5955 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5957 initialPosition[1][j] = WhiteRook;
5958 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5961 if( nrCastlingRights == -1) {
5962 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5963 /* This sets default castling rights from none to normal corners */
5964 /* Variants with other castling rights must set them themselves above */
5965 nrCastlingRights = 6;
5967 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5968 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5969 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5970 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5971 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5972 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5975 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5976 if(gameInfo.variant == VariantGreat) { // promotion commoners
5977 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5978 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5979 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5980 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5982 if( gameInfo.variant == VariantSChess ) {
5983 initialPosition[1][0] = BlackMarshall;
5984 initialPosition[2][0] = BlackAngel;
5985 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5986 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5987 initialPosition[1][1] = initialPosition[2][1] =
5988 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5990 if (appData.debugMode) {
5991 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5993 if(shuffleOpenings) {
5994 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5995 startedFromSetupPosition = TRUE;
5997 if(startedFromPositionFile) {
5998 /* [HGM] loadPos: use PositionFile for every new game */
5999 CopyBoard(initialPosition, filePosition);
6000 for(i=0; i<nrCastlingRights; i++)
6001 initialRights[i] = filePosition[CASTLING][i];
6002 startedFromSetupPosition = TRUE;
6005 CopyBoard(boards[0], initialPosition);
6007 if(oldx != gameInfo.boardWidth ||
6008 oldy != gameInfo.boardHeight ||
6009 oldv != gameInfo.variant ||
6010 oldh != gameInfo.holdingsWidth
6012 InitDrawingSizes(-2 ,0);
6014 oldv = gameInfo.variant;
6016 DrawPosition(TRUE, boards[currentMove]);
6020 SendBoard (ChessProgramState *cps, int moveNum)
6022 char message[MSG_SIZ];
6024 if (cps->useSetboard) {
6025 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6026 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6027 SendToProgram(message, cps);
6032 int i, j, left=0, right=BOARD_WIDTH;
6033 /* Kludge to set black to move, avoiding the troublesome and now
6034 * deprecated "black" command.
6036 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6037 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6039 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6041 SendToProgram("edit\n", cps);
6042 SendToProgram("#\n", cps);
6043 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6044 bp = &boards[moveNum][i][left];
6045 for (j = left; j < right; j++, bp++) {
6046 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6047 if ((int) *bp < (int) BlackPawn) {
6048 if(j == BOARD_RGHT+1)
6049 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6050 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6051 if(message[0] == '+' || message[0] == '~') {
6052 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6053 PieceToChar((ChessSquare)(DEMOTED *bp)),
6056 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6057 message[1] = BOARD_RGHT - 1 - j + '1';
6058 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6060 SendToProgram(message, cps);
6065 SendToProgram("c\n", cps);
6066 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6067 bp = &boards[moveNum][i][left];
6068 for (j = left; j < right; j++, bp++) {
6069 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6070 if (((int) *bp != (int) EmptySquare)
6071 && ((int) *bp >= (int) BlackPawn)) {
6072 if(j == BOARD_LEFT-2)
6073 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6074 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6076 if(message[0] == '+' || message[0] == '~') {
6077 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078 PieceToChar((ChessSquare)(DEMOTED *bp)),
6081 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082 message[1] = BOARD_RGHT - 1 - j + '1';
6083 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6085 SendToProgram(message, cps);
6090 SendToProgram(".\n", cps);
6092 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6096 DefaultPromoChoice (int white)
6099 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6100 result = WhiteFerz; // no choice
6101 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6102 result= WhiteKing; // in Suicide Q is the last thing we want
6103 else if(gameInfo.variant == VariantSpartan)
6104 result = white ? WhiteQueen : WhiteAngel;
6105 else result = WhiteQueen;
6106 if(!white) result = WHITE_TO_BLACK result;
6110 static int autoQueen; // [HGM] oneclick
6113 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6115 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6116 /* [HGM] add Shogi promotions */
6117 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6122 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6123 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6125 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6126 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6129 piece = boards[currentMove][fromY][fromX];
6130 if(gameInfo.variant == VariantShogi) {
6131 promotionZoneSize = BOARD_HEIGHT/3;
6132 highestPromotingPiece = (int)WhiteFerz;
6133 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6134 promotionZoneSize = 3;
6137 // Treat Lance as Pawn when it is not representing Amazon
6138 if(gameInfo.variant != VariantSuper) {
6139 if(piece == WhiteLance) piece = WhitePawn; else
6140 if(piece == BlackLance) piece = BlackPawn;
6143 // next weed out all moves that do not touch the promotion zone at all
6144 if((int)piece >= BlackPawn) {
6145 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6147 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6149 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6150 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6153 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6155 // weed out mandatory Shogi promotions
6156 if(gameInfo.variant == VariantShogi) {
6157 if(piece >= BlackPawn) {
6158 if(toY == 0 && piece == BlackPawn ||
6159 toY == 0 && piece == BlackQueen ||
6160 toY <= 1 && piece == BlackKnight) {
6165 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6166 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6167 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6174 // weed out obviously illegal Pawn moves
6175 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6176 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6177 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6178 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6179 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6180 // note we are not allowed to test for valid (non-)capture, due to premove
6183 // we either have a choice what to promote to, or (in Shogi) whether to promote
6184 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6185 *promoChoice = PieceToChar(BlackFerz); // no choice
6188 // no sense asking what we must promote to if it is going to explode...
6189 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6190 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6193 // give caller the default choice even if we will not make it
6194 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6195 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6196 if( sweepSelect && gameInfo.variant != VariantGreat
6197 && gameInfo.variant != VariantGrand
6198 && gameInfo.variant != VariantSuper) return FALSE;
6199 if(autoQueen) return FALSE; // predetermined
6201 // suppress promotion popup on illegal moves that are not premoves
6202 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6203 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6204 if(appData.testLegality && !premove) {
6205 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6206 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6207 if(moveType != WhitePromotion && moveType != BlackPromotion)
6215 InPalace (int row, int column)
6216 { /* [HGM] for Xiangqi */
6217 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6218 column < (BOARD_WIDTH + 4)/2 &&
6219 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6224 PieceForSquare (int x, int y)
6226 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6229 return boards[currentMove][y][x];
6233 OKToStartUserMove (int x, int y)
6235 ChessSquare from_piece;
6238 if (matchMode) return FALSE;
6239 if (gameMode == EditPosition) return TRUE;
6241 if (x >= 0 && y >= 0)
6242 from_piece = boards[currentMove][y][x];
6244 from_piece = EmptySquare;
6246 if (from_piece == EmptySquare) return FALSE;
6248 white_piece = (int)from_piece >= (int)WhitePawn &&
6249 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6253 case TwoMachinesPlay:
6261 case MachinePlaysWhite:
6262 case IcsPlayingBlack:
6263 if (appData.zippyPlay) return FALSE;
6265 DisplayMoveError(_("You are playing Black"));
6270 case MachinePlaysBlack:
6271 case IcsPlayingWhite:
6272 if (appData.zippyPlay) return FALSE;
6274 DisplayMoveError(_("You are playing White"));
6279 case PlayFromGameFile:
6280 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6282 if (!white_piece && WhiteOnMove(currentMove)) {
6283 DisplayMoveError(_("It is White's turn"));
6286 if (white_piece && !WhiteOnMove(currentMove)) {
6287 DisplayMoveError(_("It is Black's turn"));
6290 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6291 /* Editing correspondence game history */
6292 /* Could disallow this or prompt for confirmation */
6297 case BeginningOfGame:
6298 if (appData.icsActive) return FALSE;
6299 if (!appData.noChessProgram) {
6301 DisplayMoveError(_("You are playing White"));
6308 if (!white_piece && WhiteOnMove(currentMove)) {
6309 DisplayMoveError(_("It is White's turn"));
6312 if (white_piece && !WhiteOnMove(currentMove)) {
6313 DisplayMoveError(_("It is Black's turn"));
6322 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6323 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6324 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6325 && gameMode != AnalyzeFile && gameMode != Training) {
6326 DisplayMoveError(_("Displayed position is not current"));
6333 OnlyMove (int *x, int *y, Boolean captures)
6335 DisambiguateClosure cl;
6336 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6338 case MachinePlaysBlack:
6339 case IcsPlayingWhite:
6340 case BeginningOfGame:
6341 if(!WhiteOnMove(currentMove)) return FALSE;
6343 case MachinePlaysWhite:
6344 case IcsPlayingBlack:
6345 if(WhiteOnMove(currentMove)) return FALSE;
6352 cl.pieceIn = EmptySquare;
6357 cl.promoCharIn = NULLCHAR;
6358 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6359 if( cl.kind == NormalMove ||
6360 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6361 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6362 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6369 if(cl.kind != ImpossibleMove) return FALSE;
6370 cl.pieceIn = EmptySquare;
6375 cl.promoCharIn = NULLCHAR;
6376 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6377 if( cl.kind == NormalMove ||
6378 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6379 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6380 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6385 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6391 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6392 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6393 int lastLoadGameUseList = FALSE;
6394 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6395 ChessMove lastLoadGameStart = EndOfFile;
6398 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6401 ChessSquare pdown, pup;
6403 /* Check if the user is playing in turn. This is complicated because we
6404 let the user "pick up" a piece before it is his turn. So the piece he
6405 tried to pick up may have been captured by the time he puts it down!
6406 Therefore we use the color the user is supposed to be playing in this
6407 test, not the color of the piece that is currently on the starting
6408 square---except in EditGame mode, where the user is playing both
6409 sides; fortunately there the capture race can't happen. (It can
6410 now happen in IcsExamining mode, but that's just too bad. The user
6411 will get a somewhat confusing message in that case.)
6416 case TwoMachinesPlay:
6420 /* We switched into a game mode where moves are not accepted,
6421 perhaps while the mouse button was down. */
6424 case MachinePlaysWhite:
6425 /* User is moving for Black */
6426 if (WhiteOnMove(currentMove)) {
6427 DisplayMoveError(_("It is White's turn"));
6432 case MachinePlaysBlack:
6433 /* User is moving for White */
6434 if (!WhiteOnMove(currentMove)) {
6435 DisplayMoveError(_("It is Black's turn"));
6440 case PlayFromGameFile:
6441 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6444 case BeginningOfGame:
6447 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6448 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6449 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6450 /* User is moving for Black */
6451 if (WhiteOnMove(currentMove)) {
6452 DisplayMoveError(_("It is White's turn"));
6456 /* User is moving for White */
6457 if (!WhiteOnMove(currentMove)) {
6458 DisplayMoveError(_("It is Black's turn"));
6464 case IcsPlayingBlack:
6465 /* User is moving for Black */
6466 if (WhiteOnMove(currentMove)) {
6467 if (!appData.premove) {
6468 DisplayMoveError(_("It is White's turn"));
6469 } else if (toX >= 0 && toY >= 0) {
6472 premoveFromX = fromX;
6473 premoveFromY = fromY;
6474 premovePromoChar = promoChar;
6476 if (appData.debugMode)
6477 fprintf(debugFP, "Got premove: fromX %d,"
6478 "fromY %d, toX %d, toY %d\n",
6479 fromX, fromY, toX, toY);
6485 case IcsPlayingWhite:
6486 /* User is moving for White */
6487 if (!WhiteOnMove(currentMove)) {
6488 if (!appData.premove) {
6489 DisplayMoveError(_("It is Black's turn"));
6490 } else if (toX >= 0 && toY >= 0) {
6493 premoveFromX = fromX;
6494 premoveFromY = fromY;
6495 premovePromoChar = promoChar;
6497 if (appData.debugMode)
6498 fprintf(debugFP, "Got premove: fromX %d,"
6499 "fromY %d, toX %d, toY %d\n",
6500 fromX, fromY, toX, toY);
6510 /* EditPosition, empty square, or different color piece;
6511 click-click move is possible */
6512 if (toX == -2 || toY == -2) {
6513 boards[0][fromY][fromX] = EmptySquare;
6514 DrawPosition(FALSE, boards[currentMove]);
6516 } else if (toX >= 0 && toY >= 0) {
6517 boards[0][toY][toX] = boards[0][fromY][fromX];
6518 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6519 if(boards[0][fromY][0] != EmptySquare) {
6520 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6521 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6524 if(fromX == BOARD_RGHT+1) {
6525 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6526 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6527 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6530 boards[0][fromY][fromX] = EmptySquare;
6531 DrawPosition(FALSE, boards[currentMove]);
6537 if(toX < 0 || toY < 0) return;
6538 pdown = boards[currentMove][fromY][fromX];
6539 pup = boards[currentMove][toY][toX];
6541 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6542 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6543 if( pup != EmptySquare ) return;
6544 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6545 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6546 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6547 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6548 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6549 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6550 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6554 /* [HGM] always test for legality, to get promotion info */
6555 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6556 fromY, fromX, toY, toX, promoChar);
6558 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6560 /* [HGM] but possibly ignore an IllegalMove result */
6561 if (appData.testLegality) {
6562 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6563 DisplayMoveError(_("Illegal move"));
6568 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6571 /* Common tail of UserMoveEvent and DropMenuEvent */
6573 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6577 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6578 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6579 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6580 if(WhiteOnMove(currentMove)) {
6581 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6583 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6587 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6588 move type in caller when we know the move is a legal promotion */
6589 if(moveType == NormalMove && promoChar)
6590 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6592 /* [HGM] <popupFix> The following if has been moved here from
6593 UserMoveEvent(). Because it seemed to belong here (why not allow
6594 piece drops in training games?), and because it can only be
6595 performed after it is known to what we promote. */
6596 if (gameMode == Training) {
6597 /* compare the move played on the board to the next move in the
6598 * game. If they match, display the move and the opponent's response.
6599 * If they don't match, display an error message.
6603 CopyBoard(testBoard, boards[currentMove]);
6604 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6606 if (CompareBoards(testBoard, boards[currentMove+1])) {
6607 ForwardInner(currentMove+1);
6609 /* Autoplay the opponent's response.
6610 * if appData.animate was TRUE when Training mode was entered,
6611 * the response will be animated.
6613 saveAnimate = appData.animate;
6614 appData.animate = animateTraining;
6615 ForwardInner(currentMove+1);
6616 appData.animate = saveAnimate;
6618 /* check for the end of the game */
6619 if (currentMove >= forwardMostMove) {
6620 gameMode = PlayFromGameFile;
6622 SetTrainingModeOff();
6623 DisplayInformation(_("End of game"));
6626 DisplayError(_("Incorrect move"), 0);
6631 /* Ok, now we know that the move is good, so we can kill
6632 the previous line in Analysis Mode */
6633 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6634 && currentMove < forwardMostMove) {
6635 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6636 else forwardMostMove = currentMove;
6639 /* If we need the chess program but it's dead, restart it */
6640 ResurrectChessProgram();
6642 /* A user move restarts a paused game*/
6646 thinkOutput[0] = NULLCHAR;
6648 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6650 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6651 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655 if (gameMode == BeginningOfGame) {
6656 if (appData.noChessProgram) {
6657 gameMode = EditGame;
6661 gameMode = MachinePlaysBlack;
6664 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6666 if (first.sendName) {
6667 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6668 SendToProgram(buf, &first);
6675 /* Relay move to ICS or chess engine */
6676 if (appData.icsActive) {
6677 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6678 gameMode == IcsExamining) {
6679 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6680 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6682 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6684 // also send plain move, in case ICS does not understand atomic claims
6685 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6689 if (first.sendTime && (gameMode == BeginningOfGame ||
6690 gameMode == MachinePlaysWhite ||
6691 gameMode == MachinePlaysBlack)) {
6692 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6694 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6695 // [HGM] book: if program might be playing, let it use book
6696 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6697 first.maybeThinking = TRUE;
6698 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6699 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6700 SendBoard(&first, currentMove+1);
6701 } else SendMoveToProgram(forwardMostMove-1, &first);
6702 if (currentMove == cmailOldMove + 1) {
6703 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6707 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711 if(appData.testLegality)
6712 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6718 if (WhiteOnMove(currentMove)) {
6719 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6721 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6725 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6730 case MachinePlaysBlack:
6731 case MachinePlaysWhite:
6732 /* disable certain menu options while machine is thinking */
6733 SetMachineThinkingEnables();
6740 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6741 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6743 if(bookHit) { // [HGM] book: simulate book reply
6744 static char bookMove[MSG_SIZ]; // a bit generous?
6746 programStats.nodes = programStats.depth = programStats.time =
6747 programStats.score = programStats.got_only_move = 0;
6748 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6750 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6751 strcat(bookMove, bookHit);
6752 HandleMachineMove(bookMove, &first);
6758 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6760 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6761 Markers *m = (Markers *) closure;
6762 if(rf == fromY && ff == fromX)
6763 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6764 || kind == WhiteCapturesEnPassant
6765 || kind == BlackCapturesEnPassant);
6766 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6770 MarkTargetSquares (int clear)
6773 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6774 !appData.testLegality || gameMode == EditPosition) return;
6776 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6779 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6780 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6781 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6783 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6786 DrawPosition(TRUE, NULL);
6790 Explode (Board board, int fromX, int fromY, int toX, int toY)
6792 if(gameInfo.variant == VariantAtomic &&
6793 (board[toY][toX] != EmptySquare || // capture?
6794 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6795 board[fromY][fromX] == BlackPawn )
6797 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6803 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6806 CanPromote (ChessSquare piece, int y)
6808 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6809 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6810 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6811 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6812 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6813 gameInfo.variant == VariantMakruk) return FALSE;
6814 return (piece == BlackPawn && y == 1 ||
6815 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6816 piece == BlackLance && y == 1 ||
6817 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6821 LeftClick (ClickType clickType, int xPix, int yPix)
6824 Boolean saveAnimate;
6825 static int second = 0, promotionChoice = 0, clearFlag = 0;
6826 char promoChoice = NULLCHAR;
6829 if(appData.seekGraph && appData.icsActive && loggedOn &&
6830 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6831 SeekGraphClick(clickType, xPix, yPix, 0);
6835 if (clickType == Press) ErrorPopDown();
6837 x = EventToSquare(xPix, BOARD_WIDTH);
6838 y = EventToSquare(yPix, BOARD_HEIGHT);
6839 if (!flipView && y >= 0) {
6840 y = BOARD_HEIGHT - 1 - y;
6842 if (flipView && x >= 0) {
6843 x = BOARD_WIDTH - 1 - x;
6846 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6847 defaultPromoChoice = promoSweep;
6848 promoSweep = EmptySquare; // terminate sweep
6849 promoDefaultAltered = TRUE;
6850 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6853 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6854 if(clickType == Release) return; // ignore upclick of click-click destination
6855 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6856 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6857 if(gameInfo.holdingsWidth &&
6858 (WhiteOnMove(currentMove)
6859 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6860 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6861 // click in right holdings, for determining promotion piece
6862 ChessSquare p = boards[currentMove][y][x];
6863 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6864 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6865 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6866 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6871 DrawPosition(FALSE, boards[currentMove]);
6875 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6876 if(clickType == Press
6877 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6878 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6879 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6882 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6883 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6885 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6886 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6887 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6888 defaultPromoChoice = DefaultPromoChoice(side);
6891 autoQueen = appData.alwaysPromoteToQueen;
6895 gatingPiece = EmptySquare;
6896 if (clickType != Press) {
6897 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6898 DragPieceEnd(xPix, yPix); dragging = 0;
6899 DrawPosition(FALSE, NULL);
6903 fromX = x; fromY = y; toX = toY = -1;
6904 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6905 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6906 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6908 if (OKToStartUserMove(fromX, fromY)) {
6910 MarkTargetSquares(0);
6911 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6912 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6913 promoSweep = defaultPromoChoice;
6914 selectFlag = 0; lastX = xPix; lastY = yPix;
6915 Sweep(0); // Pawn that is going to promote: preview promotion piece
6916 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6918 if (appData.highlightDragging) {
6919 SetHighlights(fromX, fromY, -1, -1);
6921 } else fromX = fromY = -1;
6927 if (clickType == Press && gameMode != EditPosition) {
6932 // ignore off-board to clicks
6933 if(y < 0 || x < 0) return;
6935 /* Check if clicking again on the same color piece */
6936 fromP = boards[currentMove][fromY][fromX];
6937 toP = boards[currentMove][y][x];
6938 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6939 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6940 WhitePawn <= toP && toP <= WhiteKing &&
6941 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6942 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6943 (BlackPawn <= fromP && fromP <= BlackKing &&
6944 BlackPawn <= toP && toP <= BlackKing &&
6945 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6946 !(fromP == BlackKing && toP == BlackRook && frc))) {
6947 /* Clicked again on same color piece -- changed his mind */
6948 second = (x == fromX && y == fromY);
6949 promoDefaultAltered = FALSE;
6950 MarkTargetSquares(1);
6951 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6952 if (appData.highlightDragging) {
6953 SetHighlights(x, y, -1, -1);
6957 if (OKToStartUserMove(x, y)) {
6958 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6959 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6960 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6961 gatingPiece = boards[currentMove][fromY][fromX];
6962 else gatingPiece = EmptySquare;
6964 fromY = y; dragging = 1;
6965 MarkTargetSquares(0);
6966 DragPieceBegin(xPix, yPix, FALSE);
6967 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6968 promoSweep = defaultPromoChoice;
6969 selectFlag = 0; lastX = xPix; lastY = yPix;
6970 Sweep(0); // Pawn that is going to promote: preview promotion piece
6974 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6977 // ignore clicks on holdings
6978 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6981 if (clickType == Release && x == fromX && y == fromY) {
6982 DragPieceEnd(xPix, yPix); dragging = 0;
6984 // a deferred attempt to click-click move an empty square on top of a piece
6985 boards[currentMove][y][x] = EmptySquare;
6987 DrawPosition(FALSE, boards[currentMove]);
6988 fromX = fromY = -1; clearFlag = 0;
6991 if (appData.animateDragging) {
6992 /* Undo animation damage if any */
6993 DrawPosition(FALSE, NULL);
6996 /* Second up/down in same square; just abort move */
6999 gatingPiece = EmptySquare;
7002 ClearPremoveHighlights();
7004 /* First upclick in same square; start click-click mode */
7005 SetHighlights(x, y, -1, -1);
7012 /* we now have a different from- and (possibly off-board) to-square */
7013 /* Completed move */
7016 saveAnimate = appData.animate;
7017 MarkTargetSquares(1);
7018 if (clickType == Press) {
7019 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7020 // must be Edit Position mode with empty-square selected
7021 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7022 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7025 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7026 ChessSquare piece = boards[currentMove][fromY][fromX];
7027 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7028 promoSweep = defaultPromoChoice;
7029 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7030 selectFlag = 0; lastX = xPix; lastY = yPix;
7031 Sweep(0); // Pawn that is going to promote: preview promotion piece
7032 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7033 DrawPosition(FALSE, boards[currentMove]);
7036 /* Finish clickclick move */
7037 if (appData.animate || appData.highlightLastMove) {
7038 SetHighlights(fromX, fromY, toX, toY);
7043 /* Finish drag move */
7044 if (appData.highlightLastMove) {
7045 SetHighlights(fromX, fromY, toX, toY);
7049 DragPieceEnd(xPix, yPix); dragging = 0;
7050 /* Don't animate move and drag both */
7051 appData.animate = FALSE;
7054 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7055 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7056 ChessSquare piece = boards[currentMove][fromY][fromX];
7057 if(gameMode == EditPosition && piece != EmptySquare &&
7058 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7061 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7062 n = PieceToNumber(piece - (int)BlackPawn);
7063 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7064 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7065 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7067 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7068 n = PieceToNumber(piece);
7069 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7070 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7071 boards[currentMove][n][BOARD_WIDTH-2]++;
7073 boards[currentMove][fromY][fromX] = EmptySquare;
7077 DrawPosition(TRUE, boards[currentMove]);
7081 // off-board moves should not be highlighted
7082 if(x < 0 || y < 0) ClearHighlights();
7084 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7086 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7087 SetHighlights(fromX, fromY, toX, toY);
7088 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7089 // [HGM] super: promotion to captured piece selected from holdings
7090 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7091 promotionChoice = TRUE;
7092 // kludge follows to temporarily execute move on display, without promoting yet
7093 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7094 boards[currentMove][toY][toX] = p;
7095 DrawPosition(FALSE, boards[currentMove]);
7096 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7097 boards[currentMove][toY][toX] = q;
7098 DisplayMessage("Click in holdings to choose piece", "");
7103 int oldMove = currentMove;
7104 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7105 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7106 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7107 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7108 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7109 DrawPosition(TRUE, boards[currentMove]);
7112 appData.animate = saveAnimate;
7113 if (appData.animate || appData.animateDragging) {
7114 /* Undo animation damage if needed */
7115 DrawPosition(FALSE, NULL);
7120 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7121 { // front-end-free part taken out of PieceMenuPopup
7122 int whichMenu; int xSqr, ySqr;
7124 if(seekGraphUp) { // [HGM] seekgraph
7125 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7126 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7130 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7131 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7132 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7133 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7134 if(action == Press) {
7135 originalFlip = flipView;
7136 flipView = !flipView; // temporarily flip board to see game from partners perspective
7137 DrawPosition(TRUE, partnerBoard);
7138 DisplayMessage(partnerStatus, "");
7140 } else if(action == Release) {
7141 flipView = originalFlip;
7142 DrawPosition(TRUE, boards[currentMove]);
7148 xSqr = EventToSquare(x, BOARD_WIDTH);
7149 ySqr = EventToSquare(y, BOARD_HEIGHT);
7150 if (action == Release) {
7151 if(pieceSweep != EmptySquare) {
7152 EditPositionMenuEvent(pieceSweep, toX, toY);
7153 pieceSweep = EmptySquare;
7154 } else UnLoadPV(); // [HGM] pv
7156 if (action != Press) return -2; // return code to be ignored
7159 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7161 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7162 if (xSqr < 0 || ySqr < 0) return -1;
7163 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7164 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7165 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7166 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7170 if(!appData.icsEngineAnalyze) return -1;
7171 case IcsPlayingWhite:
7172 case IcsPlayingBlack:
7173 if(!appData.zippyPlay) goto noZip;
7176 case MachinePlaysWhite:
7177 case MachinePlaysBlack:
7178 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7179 if (!appData.dropMenu) {
7181 return 2; // flag front-end to grab mouse events
7183 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7184 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7187 if (xSqr < 0 || ySqr < 0) return -1;
7188 if (!appData.dropMenu || appData.testLegality &&
7189 gameInfo.variant != VariantBughouse &&
7190 gameInfo.variant != VariantCrazyhouse) return -1;
7191 whichMenu = 1; // drop menu
7197 if (((*fromX = xSqr) < 0) ||
7198 ((*fromY = ySqr) < 0)) {
7199 *fromX = *fromY = -1;
7203 *fromX = BOARD_WIDTH - 1 - *fromX;
7205 *fromY = BOARD_HEIGHT - 1 - *fromY;
7211 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7213 // char * hint = lastHint;
7214 FrontEndProgramStats stats;
7216 stats.which = cps == &first ? 0 : 1;
7217 stats.depth = cpstats->depth;
7218 stats.nodes = cpstats->nodes;
7219 stats.score = cpstats->score;
7220 stats.time = cpstats->time;
7221 stats.pv = cpstats->movelist;
7222 stats.hint = lastHint;
7223 stats.an_move_index = 0;
7224 stats.an_move_count = 0;
7226 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7227 stats.hint = cpstats->move_name;
7228 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7229 stats.an_move_count = cpstats->nr_moves;
7232 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
7234 SetProgramStats( &stats );
7238 ClearEngineOutputPane (int which)
7240 static FrontEndProgramStats dummyStats;
7241 dummyStats.which = which;
7242 dummyStats.pv = "#";
7243 SetProgramStats( &dummyStats );
7246 #define MAXPLAYERS 500
7249 TourneyStandings (int display)
7251 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7252 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7253 char result, *p, *names[MAXPLAYERS];
7255 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7256 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7257 names[0] = p = strdup(appData.participants);
7258 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7260 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7262 while(result = appData.results[nr]) {
7263 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7264 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7265 wScore = bScore = 0;
7267 case '+': wScore = 2; break;
7268 case '-': bScore = 2; break;
7269 case '=': wScore = bScore = 1; break;
7271 case '*': return strdup("busy"); // tourney not finished
7279 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7280 for(w=0; w<nPlayers; w++) {
7282 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7283 ranking[w] = b; points[w] = bScore; score[b] = -2;
7285 p = malloc(nPlayers*34+1);
7286 for(w=0; w<nPlayers && w<display; w++)
7287 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7293 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7294 { // count all piece types
7296 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7297 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7298 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7301 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7302 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7303 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7304 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7305 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7306 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7311 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7313 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7314 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7316 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7317 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7318 if(myPawns == 2 && nMine == 3) // KPP
7319 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7320 if(myPawns == 1 && nMine == 2) // KP
7321 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7322 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7323 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7324 if(myPawns) return FALSE;
7325 if(pCnt[WhiteRook+side])
7326 return pCnt[BlackRook-side] ||
7327 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7328 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7329 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7330 if(pCnt[WhiteCannon+side]) {
7331 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7332 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7334 if(pCnt[WhiteKnight+side])
7335 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7340 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7342 VariantClass v = gameInfo.variant;
7344 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7345 if(v == VariantShatranj) return TRUE; // always winnable through baring
7346 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7347 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7349 if(v == VariantXiangqi) {
7350 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7352 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7353 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7354 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7355 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7356 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7357 if(stale) // we have at least one last-rank P plus perhaps C
7358 return majors // KPKX
7359 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7361 return pCnt[WhiteFerz+side] // KCAK
7362 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7363 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7364 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7366 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7367 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7369 if(nMine == 1) return FALSE; // bare King
7370 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
7371 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7372 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7373 // by now we have King + 1 piece (or multiple Bishops on the same color)
7374 if(pCnt[WhiteKnight+side])
7375 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7376 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7377 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7379 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7380 if(pCnt[WhiteAlfil+side])
7381 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7382 if(pCnt[WhiteWazir+side])
7383 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7390 CompareWithRights (Board b1, Board b2)
7393 if(!CompareBoards(b1, b2)) return FALSE;
7394 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7395 /* compare castling rights */
7396 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7397 rights++; /* King lost rights, while rook still had them */
7398 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7399 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7400 rights++; /* but at least one rook lost them */
7402 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7404 if( b1[CASTLING][5] != NoRights ) {
7405 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7412 Adjudicate (ChessProgramState *cps)
7413 { // [HGM] some adjudications useful with buggy engines
7414 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7415 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7416 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7417 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7418 int k, count = 0; static int bare = 1;
7419 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7420 Boolean canAdjudicate = !appData.icsActive;
7422 // most tests only when we understand the game, i.e. legality-checking on
7423 if( appData.testLegality )
7424 { /* [HGM] Some more adjudications for obstinate engines */
7425 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7426 static int moveCount = 6;
7428 char *reason = NULL;
7430 /* Count what is on board. */
7431 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7433 /* Some material-based adjudications that have to be made before stalemate test */
7434 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7435 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7436 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7437 if(canAdjudicate && appData.checkMates) {
7439 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7440 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7441 "Xboard adjudication: King destroyed", GE_XBOARD );
7446 /* Bare King in Shatranj (loses) or Losers (wins) */
7447 if( nrW == 1 || nrB == 1) {
7448 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7449 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7450 if(canAdjudicate && appData.checkMates) {
7452 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7453 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7454 "Xboard adjudication: Bare king", GE_XBOARD );
7458 if( gameInfo.variant == VariantShatranj && --bare < 0)
7460 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7461 if(canAdjudicate && appData.checkMates) {
7462 /* but only adjudicate if adjudication enabled */
7464 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7465 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7466 "Xboard adjudication: Bare king", GE_XBOARD );
7473 // don't wait for engine to announce game end if we can judge ourselves
7474 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7476 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7477 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7478 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7479 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7482 reason = "Xboard adjudication: 3rd check";
7483 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7493 reason = "Xboard adjudication: Stalemate";
7494 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7495 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7496 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7497 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7498 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7499 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7500 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7501 EP_CHECKMATE : EP_WINS);
7502 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7503 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7507 reason = "Xboard adjudication: Checkmate";
7508 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7512 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7514 result = GameIsDrawn; break;
7516 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7518 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7522 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7524 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7525 GameEnds( result, reason, GE_XBOARD );
7529 /* Next absolutely insufficient mating material. */
7530 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7531 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7532 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7534 /* always flag draws, for judging claims */
7535 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7537 if(canAdjudicate && appData.materialDraws) {
7538 /* but only adjudicate them if adjudication enabled */
7539 if(engineOpponent) {
7540 SendToProgram("force\n", engineOpponent); // suppress reply
7541 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7543 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7548 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7549 if(gameInfo.variant == VariantXiangqi ?
7550 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7552 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7553 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7554 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7555 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7557 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7558 { /* if the first 3 moves do not show a tactical win, declare draw */
7559 if(engineOpponent) {
7560 SendToProgram("force\n", engineOpponent); // suppress reply
7561 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7563 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7566 } else moveCount = 6;
7569 // Repetition draws and 50-move rule can be applied independently of legality testing
7571 /* Check for rep-draws */
7573 for(k = forwardMostMove-2;
7574 k>=backwardMostMove && k>=forwardMostMove-100 &&
7575 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7576 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7579 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7580 /* compare castling rights */
7581 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7582 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7583 rights++; /* King lost rights, while rook still had them */
7584 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7585 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7586 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7587 rights++; /* but at least one rook lost them */
7589 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7590 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7592 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7593 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7594 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7597 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7598 && appData.drawRepeats > 1) {
7599 /* adjudicate after user-specified nr of repeats */
7600 int result = GameIsDrawn;
7601 char *details = "XBoard adjudication: repetition draw";
7602 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7603 // [HGM] xiangqi: check for forbidden perpetuals
7604 int m, ourPerpetual = 1, hisPerpetual = 1;
7605 for(m=forwardMostMove; m>k; m-=2) {
7606 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7607 ourPerpetual = 0; // the current mover did not always check
7608 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7609 hisPerpetual = 0; // the opponent did not always check
7611 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7612 ourPerpetual, hisPerpetual);
7613 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7614 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7615 details = "Xboard adjudication: perpetual checking";
7617 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7618 break; // (or we would have caught him before). Abort repetition-checking loop.
7620 // Now check for perpetual chases
7621 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7622 hisPerpetual = PerpetualChase(k, forwardMostMove);
7623 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7624 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7625 static char resdet[MSG_SIZ];
7626 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7628 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7630 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7631 break; // Abort repetition-checking loop.
7633 // if neither of us is checking or chasing all the time, or both are, it is draw
7635 if(engineOpponent) {
7636 SendToProgram("force\n", engineOpponent); // suppress reply
7637 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7639 GameEnds( result, details, GE_XBOARD );
7642 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7643 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7647 /* Now we test for 50-move draws. Determine ply count */
7648 count = forwardMostMove;
7649 /* look for last irreversble move */
7650 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7652 /* if we hit starting position, add initial plies */
7653 if( count == backwardMostMove )
7654 count -= initialRulePlies;
7655 count = forwardMostMove - count;
7656 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7657 // adjust reversible move counter for checks in Xiangqi
7658 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7659 if(i < backwardMostMove) i = backwardMostMove;
7660 while(i <= forwardMostMove) {
7661 lastCheck = inCheck; // check evasion does not count
7662 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7663 if(inCheck || lastCheck) count--; // check does not count
7668 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7669 /* this is used to judge if draw claims are legal */
7670 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7671 if(engineOpponent) {
7672 SendToProgram("force\n", engineOpponent); // suppress reply
7673 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7675 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7679 /* if draw offer is pending, treat it as a draw claim
7680 * when draw condition present, to allow engines a way to
7681 * claim draws before making their move to avoid a race
7682 * condition occurring after their move
7684 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7686 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7687 p = "Draw claim: 50-move rule";
7688 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7689 p = "Draw claim: 3-fold repetition";
7690 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7691 p = "Draw claim: insufficient mating material";
7692 if( p != NULL && canAdjudicate) {
7693 if(engineOpponent) {
7694 SendToProgram("force\n", engineOpponent); // suppress reply
7695 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7697 GameEnds( GameIsDrawn, p, GE_XBOARD );
7702 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7703 if(engineOpponent) {
7704 SendToProgram("force\n", engineOpponent); // suppress reply
7705 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7714 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7715 { // [HGM] book: this routine intercepts moves to simulate book replies
7716 char *bookHit = NULL;
7718 //first determine if the incoming move brings opponent into his book
7719 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7720 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7721 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7722 if(bookHit != NULL && !cps->bookSuspend) {
7723 // make sure opponent is not going to reply after receiving move to book position
7724 SendToProgram("force\n", cps);
7725 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7727 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7728 // now arrange restart after book miss
7730 // after a book hit we never send 'go', and the code after the call to this routine
7731 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7732 char buf[MSG_SIZ], *move = bookHit;
7734 int fromX, fromY, toX, toY;
7738 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7739 &fromX, &fromY, &toX, &toY, &promoChar)) {
7740 (void) CoordsToAlgebraic(boards[forwardMostMove],
7741 PosFlags(forwardMostMove),
7742 fromY, fromX, toY, toX, promoChar, move);
7744 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7749 SendToProgram(buf, cps);
7750 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7751 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7752 SendToProgram("go\n", cps);
7753 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7754 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7755 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7756 SendToProgram("go\n", cps);
7757 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7759 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 ChessProgramState *savedState;
7765 DeferredBookMove (void)
7767 if(savedState->lastPing != savedState->lastPong)
7768 ScheduleDelayedEvent(DeferredBookMove, 10);
7770 HandleMachineMove(savedMessage, savedState);
7773 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7776 HandleMachineMove (char *message, ChessProgramState *cps)
7778 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7779 char realname[MSG_SIZ];
7780 int fromX, fromY, toX, toY;
7787 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7788 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7789 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7790 DisplayError(_("Invalid pairing from pairing engine"), 0);
7793 pairingReceived = 1;
7795 return; // Skim the pairing messages here.
7800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7802 * Kludge to ignore BEL characters
7804 while (*message == '\007') message++;
7807 * [HGM] engine debug message: ignore lines starting with '#' character
7809 if(cps->debug && *message == '#') return;
7812 * Look for book output
7814 if (cps == &first && bookRequested) {
7815 if (message[0] == '\t' || message[0] == ' ') {
7816 /* Part of the book output is here; append it */
7817 strcat(bookOutput, message);
7818 strcat(bookOutput, " \n");
7820 } else if (bookOutput[0] != NULLCHAR) {
7821 /* All of book output has arrived; display it */
7822 char *p = bookOutput;
7823 while (*p != NULLCHAR) {
7824 if (*p == '\t') *p = ' ';
7827 DisplayInformation(bookOutput);
7828 bookRequested = FALSE;
7829 /* Fall through to parse the current output */
7834 * Look for machine move.
7836 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7837 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7839 /* This method is only useful on engines that support ping */
7840 if (cps->lastPing != cps->lastPong) {
7841 if (gameMode == BeginningOfGame) {
7842 /* Extra move from before last new; ignore */
7843 if (appData.debugMode) {
7844 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7847 if (appData.debugMode) {
7848 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7849 cps->which, gameMode);
7852 SendToProgram("undo\n", cps);
7858 case BeginningOfGame:
7859 /* Extra move from before last reset; ignore */
7860 if (appData.debugMode) {
7861 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7868 /* Extra move after we tried to stop. The mode test is
7869 not a reliable way of detecting this problem, but it's
7870 the best we can do on engines that don't support ping.
7872 if (appData.debugMode) {
7873 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7874 cps->which, gameMode);
7876 SendToProgram("undo\n", cps);
7879 case MachinePlaysWhite:
7880 case IcsPlayingWhite:
7881 machineWhite = TRUE;
7884 case MachinePlaysBlack:
7885 case IcsPlayingBlack:
7886 machineWhite = FALSE;
7889 case TwoMachinesPlay:
7890 machineWhite = (cps->twoMachinesColor[0] == 'w');
7893 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7894 if (appData.debugMode) {
7896 "Ignoring move out of turn by %s, gameMode %d"
7897 ", forwardMost %d\n",
7898 cps->which, gameMode, forwardMostMove);
7903 if(cps->alphaRank) AlphaRank(machineMove, 4);
7904 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7905 &fromX, &fromY, &toX, &toY, &promoChar)) {
7906 /* Machine move could not be parsed; ignore it. */
7907 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7908 machineMove, _(cps->which));
7909 DisplayError(buf1, 0);
7910 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7911 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7912 if (gameMode == TwoMachinesPlay) {
7913 GameEnds(machineWhite ? BlackWins : WhiteWins,
7919 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7920 /* So we have to redo legality test with true e.p. status here, */
7921 /* to make sure an illegal e.p. capture does not slip through, */
7922 /* to cause a forfeit on a justified illegal-move complaint */
7923 /* of the opponent. */
7924 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7926 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7927 fromY, fromX, toY, toX, promoChar);
7928 if(moveType == IllegalMove) {
7929 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7930 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7931 GameEnds(machineWhite ? BlackWins : WhiteWins,
7934 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7935 /* [HGM] Kludge to handle engines that send FRC-style castling
7936 when they shouldn't (like TSCP-Gothic) */
7938 case WhiteASideCastleFR:
7939 case BlackASideCastleFR:
7941 currentMoveString[2]++;
7943 case WhiteHSideCastleFR:
7944 case BlackHSideCastleFR:
7946 currentMoveString[2]--;
7948 default: ; // nothing to do, but suppresses warning of pedantic compilers
7951 hintRequested = FALSE;
7952 lastHint[0] = NULLCHAR;
7953 bookRequested = FALSE;
7954 /* Program may be pondering now */
7955 cps->maybeThinking = TRUE;
7956 if (cps->sendTime == 2) cps->sendTime = 1;
7957 if (cps->offeredDraw) cps->offeredDraw--;
7959 /* [AS] Save move info*/
7960 pvInfoList[ forwardMostMove ].score = programStats.score;
7961 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7962 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7964 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7966 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7967 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7970 while( count < adjudicateLossPlies ) {
7971 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7974 score = -score; /* Flip score for winning side */
7977 if( score > adjudicateLossThreshold ) {
7984 if( count >= adjudicateLossPlies ) {
7985 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7987 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7988 "Xboard adjudication",
7995 if(Adjudicate(cps)) {
7996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7997 return; // [HGM] adjudicate: for all automatic game ends
8001 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8003 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8004 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8006 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8008 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8010 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8011 char buf[3*MSG_SIZ];
8013 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8014 programStats.score / 100.,
8016 programStats.time / 100.,
8017 (unsigned int)programStats.nodes,
8018 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8019 programStats.movelist);
8021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8026 /* [AS] Clear stats for next move */
8027 ClearProgramStats();
8028 thinkOutput[0] = NULLCHAR;
8029 hiddenThinkOutputState = 0;
8032 if (gameMode == TwoMachinesPlay) {
8033 /* [HGM] relaying draw offers moved to after reception of move */
8034 /* and interpreting offer as claim if it brings draw condition */
8035 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8036 SendToProgram("draw\n", cps->other);
8038 if (cps->other->sendTime) {
8039 SendTimeRemaining(cps->other,
8040 cps->other->twoMachinesColor[0] == 'w');
8042 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8043 if (firstMove && !bookHit) {
8045 if (cps->other->useColors) {
8046 SendToProgram(cps->other->twoMachinesColor, cps->other);
8048 SendToProgram("go\n", cps->other);
8050 cps->other->maybeThinking = TRUE;
8053 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8055 if (!pausing && appData.ringBellAfterMoves) {
8060 * Reenable menu items that were disabled while
8061 * machine was thinking
8063 if (gameMode != TwoMachinesPlay)
8064 SetUserThinkingEnables();
8066 // [HGM] book: after book hit opponent has received move and is now in force mode
8067 // force the book reply into it, and then fake that it outputted this move by jumping
8068 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8070 static char bookMove[MSG_SIZ]; // a bit generous?
8072 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8073 strcat(bookMove, bookHit);
8076 programStats.nodes = programStats.depth = programStats.time =
8077 programStats.score = programStats.got_only_move = 0;
8078 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8080 if(cps->lastPing != cps->lastPong) {
8081 savedMessage = message; // args for deferred call
8083 ScheduleDelayedEvent(DeferredBookMove, 10);
8092 /* Set special modes for chess engines. Later something general
8093 * could be added here; for now there is just one kludge feature,
8094 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8095 * when "xboard" is given as an interactive command.
8097 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8098 cps->useSigint = FALSE;
8099 cps->useSigterm = FALSE;
8101 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8102 ParseFeatures(message+8, cps);
8103 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8106 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8107 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8108 int dummy, s=6; char buf[MSG_SIZ];
8109 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8110 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8111 if(startedFromSetupPosition) return;
8112 ParseFEN(boards[0], &dummy, message+s);
8113 DrawPosition(TRUE, boards[0]);
8114 startedFromSetupPosition = TRUE;
8117 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8118 * want this, I was asked to put it in, and obliged.
8120 if (!strncmp(message, "setboard ", 9)) {
8121 Board initial_position;
8123 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8125 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8126 DisplayError(_("Bad FEN received from engine"), 0);
8130 CopyBoard(boards[0], initial_position);
8131 initialRulePlies = FENrulePlies;
8132 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8133 else gameMode = MachinePlaysBlack;
8134 DrawPosition(FALSE, boards[currentMove]);
8140 * Look for communication commands
8142 if (!strncmp(message, "telluser ", 9)) {
8143 if(message[9] == '\\' && message[10] == '\\')
8144 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8146 DisplayNote(message + 9);
8149 if (!strncmp(message, "tellusererror ", 14)) {
8151 if(message[14] == '\\' && message[15] == '\\')
8152 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8154 DisplayError(message + 14, 0);
8157 if (!strncmp(message, "tellopponent ", 13)) {
8158 if (appData.icsActive) {
8160 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164 DisplayNote(message + 13);
8168 if (!strncmp(message, "tellothers ", 11)) {
8169 if (appData.icsActive) {
8171 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8177 if (!strncmp(message, "tellall ", 8)) {
8178 if (appData.icsActive) {
8180 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184 DisplayNote(message + 8);
8188 if (strncmp(message, "warning", 7) == 0) {
8189 /* Undocumented feature, use tellusererror in new code */
8190 DisplayError(message, 0);
8193 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8194 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8195 strcat(realname, " query");
8196 AskQuestion(realname, buf2, buf1, cps->pr);
8199 /* Commands from the engine directly to ICS. We don't allow these to be
8200 * sent until we are logged on. Crafty kibitzes have been known to
8201 * interfere with the login process.
8204 if (!strncmp(message, "tellics ", 8)) {
8205 SendToICS(message + 8);
8209 if (!strncmp(message, "tellicsnoalias ", 15)) {
8210 SendToICS(ics_prefix);
8211 SendToICS(message + 15);
8215 /* The following are for backward compatibility only */
8216 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8217 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8218 SendToICS(ics_prefix);
8224 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228 * If the move is illegal, cancel it and redraw the board.
8229 * Also deal with other error cases. Matching is rather loose
8230 * here to accommodate engines written before the spec.
8232 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8233 strncmp(message, "Error", 5) == 0) {
8234 if (StrStr(message, "name") ||
8235 StrStr(message, "rating") || StrStr(message, "?") ||
8236 StrStr(message, "result") || StrStr(message, "board") ||
8237 StrStr(message, "bk") || StrStr(message, "computer") ||
8238 StrStr(message, "variant") || StrStr(message, "hint") ||
8239 StrStr(message, "random") || StrStr(message, "depth") ||
8240 StrStr(message, "accepted")) {
8243 if (StrStr(message, "protover")) {
8244 /* Program is responding to input, so it's apparently done
8245 initializing, and this error message indicates it is
8246 protocol version 1. So we don't need to wait any longer
8247 for it to initialize and send feature commands. */
8248 FeatureDone(cps, 1);
8249 cps->protocolVersion = 1;
8252 cps->maybeThinking = FALSE;
8254 if (StrStr(message, "draw")) {
8255 /* Program doesn't have "draw" command */
8256 cps->sendDrawOffers = 0;
8259 if (cps->sendTime != 1 &&
8260 (StrStr(message, "time") || StrStr(message, "otim"))) {
8261 /* Program apparently doesn't have "time" or "otim" command */
8265 if (StrStr(message, "analyze")) {
8266 cps->analysisSupport = FALSE;
8267 cps->analyzing = FALSE;
8268 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8269 EditGameEvent(); // [HGM] try to preserve loaded game
8270 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8271 DisplayError(buf2, 0);
8274 if (StrStr(message, "(no matching move)st")) {
8275 /* Special kludge for GNU Chess 4 only */
8276 cps->stKludge = TRUE;
8277 SendTimeControl(cps, movesPerSession, timeControl,
8278 timeIncrement, appData.searchDepth,
8282 if (StrStr(message, "(no matching move)sd")) {
8283 /* Special kludge for GNU Chess 4 only */
8284 cps->sdKludge = TRUE;
8285 SendTimeControl(cps, movesPerSession, timeControl,
8286 timeIncrement, appData.searchDepth,
8290 if (!StrStr(message, "llegal")) {
8293 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8294 gameMode == IcsIdle) return;
8295 if (forwardMostMove <= backwardMostMove) return;
8296 if (pausing) PauseEvent();
8297 if(appData.forceIllegal) {
8298 // [HGM] illegal: machine refused move; force position after move into it
8299 SendToProgram("force\n", cps);
8300 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8301 // we have a real problem now, as SendBoard will use the a2a3 kludge
8302 // when black is to move, while there might be nothing on a2 or black
8303 // might already have the move. So send the board as if white has the move.
8304 // But first we must change the stm of the engine, as it refused the last move
8305 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8306 if(WhiteOnMove(forwardMostMove)) {
8307 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8308 SendBoard(cps, forwardMostMove); // kludgeless board
8310 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8311 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8312 SendBoard(cps, forwardMostMove+1); // kludgeless board
8314 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8315 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8316 gameMode == TwoMachinesPlay)
8317 SendToProgram("go\n", cps);
8320 if (gameMode == PlayFromGameFile) {
8321 /* Stop reading this game file */
8322 gameMode = EditGame;
8325 /* [HGM] illegal-move claim should forfeit game when Xboard */
8326 /* only passes fully legal moves */
8327 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8328 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8329 "False illegal-move claim", GE_XBOARD );
8330 return; // do not take back move we tested as valid
8332 currentMove = forwardMostMove-1;
8333 DisplayMove(currentMove-1); /* before DisplayMoveError */
8334 SwitchClocks(forwardMostMove-1); // [HGM] race
8335 DisplayBothClocks();
8336 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8337 parseList[currentMove], _(cps->which));
8338 DisplayMoveError(buf1);
8339 DrawPosition(FALSE, boards[currentMove]);
8341 SetUserThinkingEnables();
8344 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8345 /* Program has a broken "time" command that
8346 outputs a string not ending in newline.
8352 * If chess program startup fails, exit with an error message.
8353 * Attempts to recover here are futile.
8355 if ((StrStr(message, "unknown host") != NULL)
8356 || (StrStr(message, "No remote directory") != NULL)
8357 || (StrStr(message, "not found") != NULL)
8358 || (StrStr(message, "No such file") != NULL)
8359 || (StrStr(message, "can't alloc") != NULL)
8360 || (StrStr(message, "Permission denied") != NULL)) {
8362 cps->maybeThinking = FALSE;
8363 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8364 _(cps->which), cps->program, cps->host, message);
8365 RemoveInputSource(cps->isr);
8366 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8367 if(cps == &first) appData.noChessProgram = TRUE;
8368 DisplayError(buf1, 0);
8374 * Look for hint output
8376 if (sscanf(message, "Hint: %s", buf1) == 1) {
8377 if (cps == &first && hintRequested) {
8378 hintRequested = FALSE;
8379 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8380 &fromX, &fromY, &toX, &toY, &promoChar)) {
8381 (void) CoordsToAlgebraic(boards[forwardMostMove],
8382 PosFlags(forwardMostMove),
8383 fromY, fromX, toY, toX, promoChar, buf1);
8384 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8385 DisplayInformation(buf2);
8387 /* Hint move could not be parsed!? */
8388 snprintf(buf2, sizeof(buf2),
8389 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8390 buf1, _(cps->which));
8391 DisplayError(buf2, 0);
8394 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8400 * Ignore other messages if game is not in progress
8402 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8403 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8406 * look for win, lose, draw, or draw offer
8408 if (strncmp(message, "1-0", 3) == 0) {
8409 char *p, *q, *r = "";
8410 p = strchr(message, '{');
8418 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8420 } else if (strncmp(message, "0-1", 3) == 0) {
8421 char *p, *q, *r = "";
8422 p = strchr(message, '{');
8430 /* Kludge for Arasan 4.1 bug */
8431 if (strcmp(r, "Black resigns") == 0) {
8432 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8435 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8437 } else if (strncmp(message, "1/2", 3) == 0) {
8438 char *p, *q, *r = "";
8439 p = strchr(message, '{');
8448 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8451 } else if (strncmp(message, "White resign", 12) == 0) {
8452 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8454 } else if (strncmp(message, "Black resign", 12) == 0) {
8455 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8457 } else if (strncmp(message, "White matches", 13) == 0 ||
8458 strncmp(message, "Black matches", 13) == 0 ) {
8459 /* [HGM] ignore GNUShogi noises */
8461 } else if (strncmp(message, "White", 5) == 0 &&
8462 message[5] != '(' &&
8463 StrStr(message, "Black") == NULL) {
8464 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8466 } else if (strncmp(message, "Black", 5) == 0 &&
8467 message[5] != '(') {
8468 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8470 } else if (strcmp(message, "resign") == 0 ||
8471 strcmp(message, "computer resigns") == 0) {
8473 case MachinePlaysBlack:
8474 case IcsPlayingBlack:
8475 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8477 case MachinePlaysWhite:
8478 case IcsPlayingWhite:
8479 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8481 case TwoMachinesPlay:
8482 if (cps->twoMachinesColor[0] == 'w')
8483 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8485 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8492 } else if (strncmp(message, "opponent mates", 14) == 0) {
8494 case MachinePlaysBlack:
8495 case IcsPlayingBlack:
8496 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8498 case MachinePlaysWhite:
8499 case IcsPlayingWhite:
8500 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8502 case TwoMachinesPlay:
8503 if (cps->twoMachinesColor[0] == 'w')
8504 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8506 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8513 } else if (strncmp(message, "computer mates", 14) == 0) {
8515 case MachinePlaysBlack:
8516 case IcsPlayingBlack:
8517 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8519 case MachinePlaysWhite:
8520 case IcsPlayingWhite:
8521 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8523 case TwoMachinesPlay:
8524 if (cps->twoMachinesColor[0] == 'w')
8525 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8534 } else if (strncmp(message, "checkmate", 9) == 0) {
8535 if (WhiteOnMove(forwardMostMove)) {
8536 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8538 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8541 } else if (strstr(message, "Draw") != NULL ||
8542 strstr(message, "game is a draw") != NULL) {
8543 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8545 } else if (strstr(message, "offer") != NULL &&
8546 strstr(message, "draw") != NULL) {
8548 if (appData.zippyPlay && first.initDone) {
8549 /* Relay offer to ICS */
8550 SendToICS(ics_prefix);
8551 SendToICS("draw\n");
8554 cps->offeredDraw = 2; /* valid until this engine moves twice */
8555 if (gameMode == TwoMachinesPlay) {
8556 if (cps->other->offeredDraw) {
8557 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8558 /* [HGM] in two-machine mode we delay relaying draw offer */
8559 /* until after we also have move, to see if it is really claim */
8561 } else if (gameMode == MachinePlaysWhite ||
8562 gameMode == MachinePlaysBlack) {
8563 if (userOfferedDraw) {
8564 DisplayInformation(_("Machine accepts your draw offer"));
8565 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8567 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8574 * Look for thinking output
8576 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8577 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8579 int plylev, mvleft, mvtot, curscore, time;
8580 char mvname[MOVE_LEN];
8584 int prefixHint = FALSE;
8585 mvname[0] = NULLCHAR;
8588 case MachinePlaysBlack:
8589 case IcsPlayingBlack:
8590 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8592 case MachinePlaysWhite:
8593 case IcsPlayingWhite:
8594 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8599 case IcsObserving: /* [DM] icsEngineAnalyze */
8600 if (!appData.icsEngineAnalyze) ignore = TRUE;
8602 case TwoMachinesPlay:
8603 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8613 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8615 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8616 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8618 if (plyext != ' ' && plyext != '\t') {
8622 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8623 if( cps->scoreIsAbsolute &&
8624 ( gameMode == MachinePlaysBlack ||
8625 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8626 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8627 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8628 !WhiteOnMove(currentMove)
8631 curscore = -curscore;
8634 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8636 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8639 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8640 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8641 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8642 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8643 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8644 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8646 } else DisplayError(_("failed writing PV"), 0);
8649 tempStats.depth = plylev;
8650 tempStats.nodes = nodes;
8651 tempStats.time = time;
8652 tempStats.score = curscore;
8653 tempStats.got_only_move = 0;
8655 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8658 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8659 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8660 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8661 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8662 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8663 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8664 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8665 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8668 /* Buffer overflow protection */
8669 if (pv[0] != NULLCHAR) {
8670 if (strlen(pv) >= sizeof(tempStats.movelist)
8671 && appData.debugMode) {
8673 "PV is too long; using the first %u bytes.\n",
8674 (unsigned) sizeof(tempStats.movelist) - 1);
8677 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8679 sprintf(tempStats.movelist, " no PV\n");
8682 if (tempStats.seen_stat) {
8683 tempStats.ok_to_send = 1;
8686 if (strchr(tempStats.movelist, '(') != NULL) {
8687 tempStats.line_is_book = 1;
8688 tempStats.nr_moves = 0;
8689 tempStats.moves_left = 0;
8691 tempStats.line_is_book = 0;
8694 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8695 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8697 SendProgramStatsToFrontend( cps, &tempStats );
8700 [AS] Protect the thinkOutput buffer from overflow... this
8701 is only useful if buf1 hasn't overflowed first!
8703 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8705 (gameMode == TwoMachinesPlay ?
8706 ToUpper(cps->twoMachinesColor[0]) : ' '),
8707 ((double) curscore) / 100.0,
8708 prefixHint ? lastHint : "",
8709 prefixHint ? " " : "" );
8711 if( buf1[0] != NULLCHAR ) {
8712 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8714 if( strlen(pv) > max_len ) {
8715 if( appData.debugMode) {
8716 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8718 pv[max_len+1] = '\0';
8721 strcat( thinkOutput, pv);
8724 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8725 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8726 DisplayMove(currentMove - 1);
8730 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8731 /* crafty (9.25+) says "(only move) <move>"
8732 * if there is only 1 legal move
8734 sscanf(p, "(only move) %s", buf1);
8735 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8736 sprintf(programStats.movelist, "%s (only move)", buf1);
8737 programStats.depth = 1;
8738 programStats.nr_moves = 1;
8739 programStats.moves_left = 1;
8740 programStats.nodes = 1;
8741 programStats.time = 1;
8742 programStats.got_only_move = 1;
8744 /* Not really, but we also use this member to
8745 mean "line isn't going to change" (Crafty
8746 isn't searching, so stats won't change) */
8747 programStats.line_is_book = 1;
8749 SendProgramStatsToFrontend( cps, &programStats );
8751 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8752 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8753 DisplayMove(currentMove - 1);
8756 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8757 &time, &nodes, &plylev, &mvleft,
8758 &mvtot, mvname) >= 5) {
8759 /* The stat01: line is from Crafty (9.29+) in response
8760 to the "." command */
8761 programStats.seen_stat = 1;
8762 cps->maybeThinking = TRUE;
8764 if (programStats.got_only_move || !appData.periodicUpdates)
8767 programStats.depth = plylev;
8768 programStats.time = time;
8769 programStats.nodes = nodes;
8770 programStats.moves_left = mvleft;
8771 programStats.nr_moves = mvtot;
8772 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8773 programStats.ok_to_send = 1;
8774 programStats.movelist[0] = '\0';
8776 SendProgramStatsToFrontend( cps, &programStats );
8780 } else if (strncmp(message,"++",2) == 0) {
8781 /* Crafty 9.29+ outputs this */
8782 programStats.got_fail = 2;
8785 } else if (strncmp(message,"--",2) == 0) {
8786 /* Crafty 9.29+ outputs this */
8787 programStats.got_fail = 1;
8790 } else if (thinkOutput[0] != NULLCHAR &&
8791 strncmp(message, " ", 4) == 0) {
8792 unsigned message_len;
8795 while (*p && *p == ' ') p++;
8797 message_len = strlen( p );
8799 /* [AS] Avoid buffer overflow */
8800 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8801 strcat(thinkOutput, " ");
8802 strcat(thinkOutput, p);
8805 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8806 strcat(programStats.movelist, " ");
8807 strcat(programStats.movelist, p);
8810 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8811 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8812 DisplayMove(currentMove - 1);
8820 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8821 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8823 ChessProgramStats cpstats;
8825 if (plyext != ' ' && plyext != '\t') {
8829 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8830 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8831 curscore = -curscore;
8834 cpstats.depth = plylev;
8835 cpstats.nodes = nodes;
8836 cpstats.time = time;
8837 cpstats.score = curscore;
8838 cpstats.got_only_move = 0;
8839 cpstats.movelist[0] = '\0';
8841 if (buf1[0] != NULLCHAR) {
8842 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8845 cpstats.ok_to_send = 0;
8846 cpstats.line_is_book = 0;
8847 cpstats.nr_moves = 0;
8848 cpstats.moves_left = 0;
8850 SendProgramStatsToFrontend( cps, &cpstats );
8857 /* Parse a game score from the character string "game", and
8858 record it as the history of the current game. The game
8859 score is NOT assumed to start from the standard position.
8860 The display is not updated in any way.
8863 ParseGameHistory (char *game)
8866 int fromX, fromY, toX, toY, boardIndex;
8871 if (appData.debugMode)
8872 fprintf(debugFP, "Parsing game history: %s\n", game);
8874 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8875 gameInfo.site = StrSave(appData.icsHost);
8876 gameInfo.date = PGNDate();
8877 gameInfo.round = StrSave("-");
8879 /* Parse out names of players */
8880 while (*game == ' ') game++;
8882 while (*game != ' ') *p++ = *game++;
8884 gameInfo.white = StrSave(buf);
8885 while (*game == ' ') game++;
8887 while (*game != ' ' && *game != '\n') *p++ = *game++;
8889 gameInfo.black = StrSave(buf);
8892 boardIndex = blackPlaysFirst ? 1 : 0;
8895 yyboardindex = boardIndex;
8896 moveType = (ChessMove) Myylex();
8898 case IllegalMove: /* maybe suicide chess, etc. */
8899 if (appData.debugMode) {
8900 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8901 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8902 setbuf(debugFP, NULL);
8904 case WhitePromotion:
8905 case BlackPromotion:
8906 case WhiteNonPromotion:
8907 case BlackNonPromotion:
8909 case WhiteCapturesEnPassant:
8910 case BlackCapturesEnPassant:
8911 case WhiteKingSideCastle:
8912 case WhiteQueenSideCastle:
8913 case BlackKingSideCastle:
8914 case BlackQueenSideCastle:
8915 case WhiteKingSideCastleWild:
8916 case WhiteQueenSideCastleWild:
8917 case BlackKingSideCastleWild:
8918 case BlackQueenSideCastleWild:
8920 case WhiteHSideCastleFR:
8921 case WhiteASideCastleFR:
8922 case BlackHSideCastleFR:
8923 case BlackASideCastleFR:
8925 fromX = currentMoveString[0] - AAA;
8926 fromY = currentMoveString[1] - ONE;
8927 toX = currentMoveString[2] - AAA;
8928 toY = currentMoveString[3] - ONE;
8929 promoChar = currentMoveString[4];
8933 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8934 fromX = moveType == WhiteDrop ?
8935 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8936 (int) CharToPiece(ToLower(currentMoveString[0]));
8938 toX = currentMoveString[2] - AAA;
8939 toY = currentMoveString[3] - ONE;
8940 promoChar = NULLCHAR;
8944 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8945 if (appData.debugMode) {
8946 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8947 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8948 setbuf(debugFP, NULL);
8950 DisplayError(buf, 0);
8952 case ImpossibleMove:
8954 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8955 if (appData.debugMode) {
8956 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8957 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8958 setbuf(debugFP, NULL);
8960 DisplayError(buf, 0);
8963 if (boardIndex < backwardMostMove) {
8964 /* Oops, gap. How did that happen? */
8965 DisplayError(_("Gap in move list"), 0);
8968 backwardMostMove = blackPlaysFirst ? 1 : 0;
8969 if (boardIndex > forwardMostMove) {
8970 forwardMostMove = boardIndex;
8974 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8975 strcat(parseList[boardIndex-1], " ");
8976 strcat(parseList[boardIndex-1], yy_text);
8988 case GameUnfinished:
8989 if (gameMode == IcsExamining) {
8990 if (boardIndex < backwardMostMove) {
8991 /* Oops, gap. How did that happen? */
8994 backwardMostMove = blackPlaysFirst ? 1 : 0;
8997 gameInfo.result = moveType;
8998 p = strchr(yy_text, '{');
8999 if (p == NULL) p = strchr(yy_text, '(');
9002 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9004 q = strchr(p, *p == '{' ? '}' : ')');
9005 if (q != NULL) *q = NULLCHAR;
9008 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9009 gameInfo.resultDetails = StrSave(p);
9012 if (boardIndex >= forwardMostMove &&
9013 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9014 backwardMostMove = blackPlaysFirst ? 1 : 0;
9017 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9018 fromY, fromX, toY, toX, promoChar,
9019 parseList[boardIndex]);
9020 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9021 /* currentMoveString is set as a side-effect of yylex */
9022 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9023 strcat(moveList[boardIndex], "\n");
9025 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9026 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9032 if(gameInfo.variant != VariantShogi)
9033 strcat(parseList[boardIndex - 1], "+");
9037 strcat(parseList[boardIndex - 1], "#");
9044 /* Apply a move to the given board */
9046 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9048 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9049 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9051 /* [HGM] compute & store e.p. status and castling rights for new position */
9052 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9054 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9055 oldEP = (signed char)board[EP_STATUS];
9056 board[EP_STATUS] = EP_NONE;
9058 if (fromY == DROP_RANK) {
9060 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9061 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9064 piece = board[toY][toX] = (ChessSquare) fromX;
9068 if( board[toY][toX] != EmptySquare )
9069 board[EP_STATUS] = EP_CAPTURE;
9071 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9072 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9073 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9075 if( board[fromY][fromX] == WhitePawn ) {
9076 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9077 board[EP_STATUS] = EP_PAWN_MOVE;
9079 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9080 gameInfo.variant != VariantBerolina || toX < fromX)
9081 board[EP_STATUS] = toX | berolina;
9082 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9083 gameInfo.variant != VariantBerolina || toX > fromX)
9084 board[EP_STATUS] = toX;
9087 if( board[fromY][fromX] == BlackPawn ) {
9088 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9089 board[EP_STATUS] = EP_PAWN_MOVE;
9090 if( toY-fromY== -2) {
9091 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9092 gameInfo.variant != VariantBerolina || toX < fromX)
9093 board[EP_STATUS] = toX | berolina;
9094 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9095 gameInfo.variant != VariantBerolina || toX > fromX)
9096 board[EP_STATUS] = toX;
9100 for(i=0; i<nrCastlingRights; i++) {
9101 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9102 board[CASTLING][i] == toX && castlingRank[i] == toY
9103 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9106 if (fromX == toX && fromY == toY) return;
9108 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9109 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9110 if(gameInfo.variant == VariantKnightmate)
9111 king += (int) WhiteUnicorn - (int) WhiteKing;
9113 /* Code added by Tord: */
9114 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9115 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9116 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9117 board[fromY][fromX] = EmptySquare;
9118 board[toY][toX] = EmptySquare;
9119 if((toX > fromX) != (piece == WhiteRook)) {
9120 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9122 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9124 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9125 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9126 board[fromY][fromX] = EmptySquare;
9127 board[toY][toX] = EmptySquare;
9128 if((toX > fromX) != (piece == BlackRook)) {
9129 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9131 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9133 /* End of code added by Tord */
9135 } else if (board[fromY][fromX] == king
9136 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137 && toY == fromY && toX > fromX+1) {
9138 board[fromY][fromX] = EmptySquare;
9139 board[toY][toX] = king;
9140 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9141 board[fromY][BOARD_RGHT-1] = EmptySquare;
9142 } else if (board[fromY][fromX] == king
9143 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9144 && toY == fromY && toX < fromX-1) {
9145 board[fromY][fromX] = EmptySquare;
9146 board[toY][toX] = king;
9147 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9148 board[fromY][BOARD_LEFT] = EmptySquare;
9149 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9150 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9151 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9153 /* white pawn promotion */
9154 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9155 if(gameInfo.variant==VariantBughouse ||
9156 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9157 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9158 board[fromY][fromX] = EmptySquare;
9159 } else if ((fromY >= BOARD_HEIGHT>>1)
9161 && gameInfo.variant != VariantXiangqi
9162 && gameInfo.variant != VariantBerolina
9163 && (board[fromY][fromX] == WhitePawn)
9164 && (board[toY][toX] == EmptySquare)) {
9165 board[fromY][fromX] = EmptySquare;
9166 board[toY][toX] = WhitePawn;
9167 captured = board[toY - 1][toX];
9168 board[toY - 1][toX] = EmptySquare;
9169 } else if ((fromY == BOARD_HEIGHT-4)
9171 && gameInfo.variant == VariantBerolina
9172 && (board[fromY][fromX] == WhitePawn)
9173 && (board[toY][toX] == EmptySquare)) {
9174 board[fromY][fromX] = EmptySquare;
9175 board[toY][toX] = WhitePawn;
9176 if(oldEP & EP_BEROLIN_A) {
9177 captured = board[fromY][fromX-1];
9178 board[fromY][fromX-1] = EmptySquare;
9179 }else{ captured = board[fromY][fromX+1];
9180 board[fromY][fromX+1] = EmptySquare;
9182 } else if (board[fromY][fromX] == king
9183 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9184 && toY == fromY && toX > fromX+1) {
9185 board[fromY][fromX] = EmptySquare;
9186 board[toY][toX] = king;
9187 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9188 board[fromY][BOARD_RGHT-1] = EmptySquare;
9189 } else if (board[fromY][fromX] == king
9190 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9191 && toY == fromY && toX < fromX-1) {
9192 board[fromY][fromX] = EmptySquare;
9193 board[toY][toX] = king;
9194 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9195 board[fromY][BOARD_LEFT] = EmptySquare;
9196 } else if (fromY == 7 && fromX == 3
9197 && board[fromY][fromX] == BlackKing
9198 && toY == 7 && toX == 5) {
9199 board[fromY][fromX] = EmptySquare;
9200 board[toY][toX] = BlackKing;
9201 board[fromY][7] = EmptySquare;
9202 board[toY][4] = BlackRook;
9203 } else if (fromY == 7 && fromX == 3
9204 && board[fromY][fromX] == BlackKing
9205 && toY == 7 && toX == 1) {
9206 board[fromY][fromX] = EmptySquare;
9207 board[toY][toX] = BlackKing;
9208 board[fromY][0] = EmptySquare;
9209 board[toY][2] = BlackRook;
9210 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9211 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9212 && toY < promoRank && promoChar
9214 /* black pawn promotion */
9215 board[toY][toX] = CharToPiece(ToLower(promoChar));
9216 if(gameInfo.variant==VariantBughouse ||
9217 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9218 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9219 board[fromY][fromX] = EmptySquare;
9220 } else if ((fromY < BOARD_HEIGHT>>1)
9222 && gameInfo.variant != VariantXiangqi
9223 && gameInfo.variant != VariantBerolina
9224 && (board[fromY][fromX] == BlackPawn)
9225 && (board[toY][toX] == EmptySquare)) {
9226 board[fromY][fromX] = EmptySquare;
9227 board[toY][toX] = BlackPawn;
9228 captured = board[toY + 1][toX];
9229 board[toY + 1][toX] = EmptySquare;
9230 } else if ((fromY == 3)
9232 && gameInfo.variant == VariantBerolina
9233 && (board[fromY][fromX] == BlackPawn)
9234 && (board[toY][toX] == EmptySquare)) {
9235 board[fromY][fromX] = EmptySquare;
9236 board[toY][toX] = BlackPawn;
9237 if(oldEP & EP_BEROLIN_A) {
9238 captured = board[fromY][fromX-1];
9239 board[fromY][fromX-1] = EmptySquare;
9240 }else{ captured = board[fromY][fromX+1];
9241 board[fromY][fromX+1] = EmptySquare;
9244 board[toY][toX] = board[fromY][fromX];
9245 board[fromY][fromX] = EmptySquare;
9249 if (gameInfo.holdingsWidth != 0) {
9251 /* !!A lot more code needs to be written to support holdings */
9252 /* [HGM] OK, so I have written it. Holdings are stored in the */
9253 /* penultimate board files, so they are automaticlly stored */
9254 /* in the game history. */
9255 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9256 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9257 /* Delete from holdings, by decreasing count */
9258 /* and erasing image if necessary */
9259 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9260 if(p < (int) BlackPawn) { /* white drop */
9261 p -= (int)WhitePawn;
9262 p = PieceToNumber((ChessSquare)p);
9263 if(p >= gameInfo.holdingsSize) p = 0;
9264 if(--board[p][BOARD_WIDTH-2] <= 0)
9265 board[p][BOARD_WIDTH-1] = EmptySquare;
9266 if((int)board[p][BOARD_WIDTH-2] < 0)
9267 board[p][BOARD_WIDTH-2] = 0;
9268 } else { /* black drop */
9269 p -= (int)BlackPawn;
9270 p = PieceToNumber((ChessSquare)p);
9271 if(p >= gameInfo.holdingsSize) p = 0;
9272 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9273 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9274 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9275 board[BOARD_HEIGHT-1-p][1] = 0;
9278 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9279 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9280 /* [HGM] holdings: Add to holdings, if holdings exist */
9281 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9282 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9283 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9286 if (p >= (int) BlackPawn) {
9287 p -= (int)BlackPawn;
9288 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289 /* in Shogi restore piece to its original first */
9290 captured = (ChessSquare) (DEMOTED captured);
9293 p = PieceToNumber((ChessSquare)p);
9294 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9295 board[p][BOARD_WIDTH-2]++;
9296 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9298 p -= (int)WhitePawn;
9299 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9300 captured = (ChessSquare) (DEMOTED captured);
9303 p = PieceToNumber((ChessSquare)p);
9304 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9305 board[BOARD_HEIGHT-1-p][1]++;
9306 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9309 } else if (gameInfo.variant == VariantAtomic) {
9310 if (captured != EmptySquare) {
9312 for (y = toY-1; y <= toY+1; y++) {
9313 for (x = toX-1; x <= toX+1; x++) {
9314 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9315 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9316 board[y][x] = EmptySquare;
9320 board[toY][toX] = EmptySquare;
9323 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9324 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9326 if(promoChar == '+') {
9327 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9328 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9329 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9330 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9332 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9333 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9334 // [HGM] superchess: take promotion piece out of holdings
9335 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9336 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9337 if(!--board[k][BOARD_WIDTH-2])
9338 board[k][BOARD_WIDTH-1] = EmptySquare;
9340 if(!--board[BOARD_HEIGHT-1-k][1])
9341 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9347 /* Updates forwardMostMove */
9349 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9351 // forwardMostMove++; // [HGM] bare: moved downstream
9353 (void) CoordsToAlgebraic(boards[forwardMostMove],
9354 PosFlags(forwardMostMove),
9355 fromY, fromX, toY, toX, promoChar,
9356 parseList[forwardMostMove]);
9358 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9359 int timeLeft; static int lastLoadFlag=0; int king, piece;
9360 piece = boards[forwardMostMove][fromY][fromX];
9361 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9362 if(gameInfo.variant == VariantKnightmate)
9363 king += (int) WhiteUnicorn - (int) WhiteKing;
9364 if(forwardMostMove == 0) {
9365 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9366 fprintf(serverMoves, "%s;", UserName());
9367 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9368 fprintf(serverMoves, "%s;", second.tidy);
9369 fprintf(serverMoves, "%s;", first.tidy);
9370 if(gameMode == MachinePlaysWhite)
9371 fprintf(serverMoves, "%s;", UserName());
9372 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9373 fprintf(serverMoves, "%s;", second.tidy);
9374 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9375 lastLoadFlag = loadFlag;
9377 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9378 // print castling suffix
9379 if( toY == fromY && piece == king ) {
9381 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9383 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9386 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9387 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9388 boards[forwardMostMove][toY][toX] == EmptySquare
9389 && fromX != toX && fromY != toY)
9390 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9392 if(promoChar != NULLCHAR)
9393 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9395 char buf[MOVE_LEN*2], *p; int len;
9396 fprintf(serverMoves, "/%d/%d",
9397 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9398 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9399 else timeLeft = blackTimeRemaining/1000;
9400 fprintf(serverMoves, "/%d", timeLeft);
9401 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9402 if(p = strchr(buf, '=')) *p = NULLCHAR;
9403 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9404 fprintf(serverMoves, "/%s", buf);
9406 fflush(serverMoves);
9409 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9410 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9413 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9414 if (commentList[forwardMostMove+1] != NULL) {
9415 free(commentList[forwardMostMove+1]);
9416 commentList[forwardMostMove+1] = NULL;
9418 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9419 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9420 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9421 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9422 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9423 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9424 adjustedClock = FALSE;
9425 gameInfo.result = GameUnfinished;
9426 if (gameInfo.resultDetails != NULL) {
9427 free(gameInfo.resultDetails);
9428 gameInfo.resultDetails = NULL;
9430 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9431 moveList[forwardMostMove - 1]);
9432 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9438 if(gameInfo.variant != VariantShogi)
9439 strcat(parseList[forwardMostMove - 1], "+");
9443 strcat(parseList[forwardMostMove - 1], "#");
9449 /* Updates currentMove if not pausing */
9451 ShowMove (int fromX, int fromY, int toX, int toY)
9453 int instant = (gameMode == PlayFromGameFile) ?
9454 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9455 if(appData.noGUI) return;
9456 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9458 if (forwardMostMove == currentMove + 1) {
9459 AnimateMove(boards[forwardMostMove - 1],
9460 fromX, fromY, toX, toY);
9462 if (appData.highlightLastMove) {
9463 SetHighlights(fromX, fromY, toX, toY);
9466 currentMove = forwardMostMove;
9469 if (instant) return;
9471 DisplayMove(currentMove - 1);
9472 DrawPosition(FALSE, boards[currentMove]);
9473 DisplayBothClocks();
9474 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9478 SendEgtPath (ChessProgramState *cps)
9479 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9480 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9482 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9485 char c, *q = name+1, *r, *s;
9487 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9488 while(*p && *p != ',') *q++ = *p++;
9490 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9491 strcmp(name, ",nalimov:") == 0 ) {
9492 // take nalimov path from the menu-changeable option first, if it is defined
9493 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9494 SendToProgram(buf,cps); // send egtbpath command for nalimov
9496 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9497 (s = StrStr(appData.egtFormats, name)) != NULL) {
9498 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9499 s = r = StrStr(s, ":") + 1; // beginning of path info
9500 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9501 c = *r; *r = 0; // temporarily null-terminate path info
9502 *--q = 0; // strip of trailig ':' from name
9503 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9505 SendToProgram(buf,cps); // send egtbpath command for this format
9507 if(*p == ',') p++; // read away comma to position for next format name
9512 InitChessProgram (ChessProgramState *cps, int setup)
9513 /* setup needed to setup FRC opening position */
9515 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9516 if (appData.noChessProgram) return;
9517 hintRequested = FALSE;
9518 bookRequested = FALSE;
9520 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9521 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9522 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9523 if(cps->memSize) { /* [HGM] memory */
9524 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9525 SendToProgram(buf, cps);
9527 SendEgtPath(cps); /* [HGM] EGT */
9528 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9529 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9530 SendToProgram(buf, cps);
9533 SendToProgram(cps->initString, cps);
9534 if (gameInfo.variant != VariantNormal &&
9535 gameInfo.variant != VariantLoadable
9536 /* [HGM] also send variant if board size non-standard */
9537 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9539 char *v = VariantName(gameInfo.variant);
9540 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9541 /* [HGM] in protocol 1 we have to assume all variants valid */
9542 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9543 DisplayFatalError(buf, 0, 1);
9547 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9548 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9549 if( gameInfo.variant == VariantXiangqi )
9550 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9551 if( gameInfo.variant == VariantShogi )
9552 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9553 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9554 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9555 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9556 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9557 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9558 if( gameInfo.variant == VariantCourier )
9559 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9560 if( gameInfo.variant == VariantSuper )
9561 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9562 if( gameInfo.variant == VariantGreat )
9563 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9564 if( gameInfo.variant == VariantSChess )
9565 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9566 if( gameInfo.variant == VariantGrand )
9567 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9570 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9571 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9572 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9573 if(StrStr(cps->variants, b) == NULL) {
9574 // specific sized variant not known, check if general sizing allowed
9575 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9576 if(StrStr(cps->variants, "boardsize") == NULL) {
9577 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9578 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9579 DisplayFatalError(buf, 0, 1);
9582 /* [HGM] here we really should compare with the maximum supported board size */
9585 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9586 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9587 SendToProgram(buf, cps);
9589 currentlyInitializedVariant = gameInfo.variant;
9591 /* [HGM] send opening position in FRC to first engine */
9593 SendToProgram("force\n", cps);
9595 /* engine is now in force mode! Set flag to wake it up after first move. */
9596 setboardSpoiledMachineBlack = 1;
9600 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9601 SendToProgram(buf, cps);
9603 cps->maybeThinking = FALSE;
9604 cps->offeredDraw = 0;
9605 if (!appData.icsActive) {
9606 SendTimeControl(cps, movesPerSession, timeControl,
9607 timeIncrement, appData.searchDepth,
9610 if (appData.showThinking
9611 // [HGM] thinking: four options require thinking output to be sent
9612 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9614 SendToProgram("post\n", cps);
9616 SendToProgram("hard\n", cps);
9617 if (!appData.ponderNextMove) {
9618 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9619 it without being sure what state we are in first. "hard"
9620 is not a toggle, so that one is OK.
9622 SendToProgram("easy\n", cps);
9625 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9626 SendToProgram(buf, cps);
9628 cps->initDone = TRUE;
9629 ClearEngineOutputPane(cps == &second);
9634 StartChessProgram (ChessProgramState *cps)
9639 if (appData.noChessProgram) return;
9640 cps->initDone = FALSE;
9642 if (strcmp(cps->host, "localhost") == 0) {
9643 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9644 } else if (*appData.remoteShell == NULLCHAR) {
9645 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9647 if (*appData.remoteUser == NULLCHAR) {
9648 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9651 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9652 cps->host, appData.remoteUser, cps->program);
9654 err = StartChildProcess(buf, "", &cps->pr);
9658 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9659 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9660 if(cps != &first) return;
9661 appData.noChessProgram = TRUE;
9664 // DisplayFatalError(buf, err, 1);
9665 // cps->pr = NoProc;
9670 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9671 if (cps->protocolVersion > 1) {
9672 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9673 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9674 cps->comboCnt = 0; // and values of combo boxes
9675 SendToProgram(buf, cps);
9677 SendToProgram("xboard\n", cps);
9682 TwoMachinesEventIfReady P((void))
9684 static int curMess = 0;
9685 if (first.lastPing != first.lastPong) {
9686 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9687 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9690 if (second.lastPing != second.lastPong) {
9691 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9692 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9695 DisplayMessage("", ""); curMess = 0;
9701 MakeName (char *template)
9705 static char buf[MSG_SIZ];
9709 clock = time((time_t *)NULL);
9710 tm = localtime(&clock);
9712 while(*p++ = *template++) if(p[-1] == '%') {
9713 switch(*template++) {
9714 case 0: *p = 0; return buf;
9715 case 'Y': i = tm->tm_year+1900; break;
9716 case 'y': i = tm->tm_year-100; break;
9717 case 'M': i = tm->tm_mon+1; break;
9718 case 'd': i = tm->tm_mday; break;
9719 case 'h': i = tm->tm_hour; break;
9720 case 'm': i = tm->tm_min; break;
9721 case 's': i = tm->tm_sec; break;
9724 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9730 CountPlayers (char *p)
9733 while(p = strchr(p, '\n')) p++, n++; // count participants
9738 WriteTourneyFile (char *results, FILE *f)
9739 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9740 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9741 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9742 // create a file with tournament description
9743 fprintf(f, "-participants {%s}\n", appData.participants);
9744 fprintf(f, "-seedBase %d\n", appData.seedBase);
9745 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9746 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9747 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9748 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9749 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9750 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9751 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9752 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9753 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9754 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9755 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9756 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9758 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9760 fprintf(f, "-mps %d\n", appData.movesPerSession);
9761 fprintf(f, "-tc %s\n", appData.timeControl);
9762 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9764 fprintf(f, "-results \"%s\"\n", results);
9769 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9772 Substitute (char *participants, int expunge)
9774 int i, changed, changes=0, nPlayers=0;
9775 char *p, *q, *r, buf[MSG_SIZ];
9776 if(participants == NULL) return;
9777 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9778 r = p = participants; q = appData.participants;
9779 while(*p && *p == *q) {
9780 if(*p == '\n') r = p+1, nPlayers++;
9783 if(*p) { // difference
9784 while(*p && *p++ != '\n');
9785 while(*q && *q++ != '\n');
9787 changes = 1 + (strcmp(p, q) != 0);
9789 if(changes == 1) { // a single engine mnemonic was changed
9790 q = r; while(*q) nPlayers += (*q++ == '\n');
9791 p = buf; while(*r && (*p = *r++) != '\n') p++;
9793 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9794 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9795 if(mnemonic[i]) { // The substitute is valid
9797 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9798 flock(fileno(f), LOCK_EX);
9799 ParseArgsFromFile(f);
9800 fseek(f, 0, SEEK_SET);
9801 FREE(appData.participants); appData.participants = participants;
9802 if(expunge) { // erase results of replaced engine
9803 int len = strlen(appData.results), w, b, dummy;
9804 for(i=0; i<len; i++) {
9805 Pairing(i, nPlayers, &w, &b, &dummy);
9806 if((w == changed || b == changed) && appData.results[i] == '*') {
9807 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9812 for(i=0; i<len; i++) {
9813 Pairing(i, nPlayers, &w, &b, &dummy);
9814 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9817 WriteTourneyFile(appData.results, f);
9818 fclose(f); // release lock
9821 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9823 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9824 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9830 CreateTourney (char *name)
9833 if(matchMode && strcmp(name, appData.tourneyFile)) {
9834 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9836 if(name[0] == NULLCHAR) {
9837 if(appData.participants[0])
9838 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9841 f = fopen(name, "r");
9842 if(f) { // file exists
9843 ASSIGN(appData.tourneyFile, name);
9844 ParseArgsFromFile(f); // parse it
9846 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9847 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9848 DisplayError(_("Not enough participants"), 0);
9851 ASSIGN(appData.tourneyFile, name);
9852 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9853 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9856 appData.noChessProgram = FALSE;
9857 appData.clockMode = TRUE;
9863 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9865 char buf[MSG_SIZ], *p, *q;
9866 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9867 skip = !all && group[0]; // if group requested, we start in skip mode
9868 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9869 p = names; q = buf; header = 0;
9870 while(*p && *p != '\n') *q++ = *p++;
9874 if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9875 depth++; // we must be entering a new group
9876 if(all) continue; // suppress printing group headers when complete list requested
9878 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9880 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9881 if(engineList[i]) free(engineList[i]);
9882 engineList[i] = strdup(buf);
9883 if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9884 if(engineMnemonic[i]) free(engineMnemonic[i]);
9885 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9887 sscanf(q + 8, "%s", buf + strlen(buf));
9890 engineMnemonic[i] = strdup(buf);
9893 engineList[i] = engineMnemonic[i] = NULL;
9897 // following implemented as macro to avoid type limitations
9898 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9902 { // swap settings for first engine and other engine (so far only some selected options)
9907 SWAP(chessProgram, p)
9909 SWAP(hasOwnBookUCI, h)
9910 SWAP(protocolVersion, h)
9912 SWAP(scoreIsAbsolute, h)
9921 SetPlayer (int player, char *p)
9922 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9924 char buf[MSG_SIZ], *engineName;
9925 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9926 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9927 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9929 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9930 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9931 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9932 ParseArgsFromString(buf);
9938 char *recentEngines;
9941 RecentEngineEvent (int nr)
9944 // SwapEngines(1); // bump first to second
9945 // ReplaceEngine(&second, 1); // and load it there
9946 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9947 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9948 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9949 ReplaceEngine(&first, 0);
9950 FloatToFront(&appData.recentEngineList, command[n]);
9955 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9956 { // determine players from game number
9957 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9959 if(appData.tourneyType == 0) {
9960 roundsPerCycle = (nPlayers - 1) | 1;
9961 pairingsPerRound = nPlayers / 2;
9962 } else if(appData.tourneyType > 0) {
9963 roundsPerCycle = nPlayers - appData.tourneyType;
9964 pairingsPerRound = appData.tourneyType;
9966 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9967 gamesPerCycle = gamesPerRound * roundsPerCycle;
9968 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9969 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9970 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9971 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9972 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9973 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9975 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9976 if(appData.roundSync) *syncInterval = gamesPerRound;
9978 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9980 if(appData.tourneyType == 0) {
9981 if(curPairing == (nPlayers-1)/2 ) {
9982 *whitePlayer = curRound;
9983 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9985 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9986 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9987 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9988 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9990 } else if(appData.tourneyType > 1) {
9991 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9992 *whitePlayer = curRound + appData.tourneyType;
9993 } else if(appData.tourneyType > 0) {
9994 *whitePlayer = curPairing;
9995 *blackPlayer = curRound + appData.tourneyType;
9998 // take care of white/black alternation per round.
9999 // For cycles and games this is already taken care of by default, derived from matchGame!
10000 return curRound & 1;
10004 NextTourneyGame (int nr, int *swapColors)
10005 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10007 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10009 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10010 tf = fopen(appData.tourneyFile, "r");
10011 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10012 ParseArgsFromFile(tf); fclose(tf);
10013 InitTimeControls(); // TC might be altered from tourney file
10015 nPlayers = CountPlayers(appData.participants); // count participants
10016 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10017 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10020 p = q = appData.results;
10021 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10022 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10023 DisplayMessage(_("Waiting for other game(s)"),"");
10024 waitingForGame = TRUE;
10025 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10028 waitingForGame = FALSE;
10031 if(appData.tourneyType < 0) {
10032 if(nr>=0 && !pairingReceived) {
10034 if(pairing.pr == NoProc) {
10035 if(!appData.pairingEngine[0]) {
10036 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10039 StartChessProgram(&pairing); // starts the pairing engine
10041 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10042 SendToProgram(buf, &pairing);
10043 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10044 SendToProgram(buf, &pairing);
10045 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10047 pairingReceived = 0; // ... so we continue here
10049 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10050 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10051 matchGame = 1; roundNr = nr / syncInterval + 1;
10054 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10056 // redefine engines, engine dir, etc.
10057 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10058 if(first.pr == NoProc) {
10059 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10060 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10062 if(second.pr == NoProc) {
10064 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10065 SwapEngines(1); // and make that valid for second engine by swapping
10066 InitEngine(&second, 1);
10068 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10069 UpdateLogos(FALSE); // leave display to ModeHiglight()
10075 { // performs game initialization that does not invoke engines, and then tries to start the game
10076 int res, firstWhite, swapColors = 0;
10077 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10078 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
10080 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10081 if(strcmp(buf, currentDebugFile)) { // name has changed
10082 FILE *f = fopen(buf, "w");
10083 if(f) { // if opening the new file failed, just keep using the old one
10084 ASSIGN(currentDebugFile, buf);
10088 if(appData.serverFileName) {
10089 if(serverFP) fclose(serverFP);
10090 serverFP = fopen(appData.serverFileName, "w");
10091 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10092 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10096 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10097 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10098 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10099 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10100 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10101 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10102 Reset(FALSE, first.pr != NoProc);
10103 res = LoadGameOrPosition(matchGame); // setup game
10104 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10105 if(!res) return; // abort when bad game/pos file
10106 TwoMachinesEvent();
10110 UserAdjudicationEvent (int result)
10112 ChessMove gameResult = GameIsDrawn;
10115 gameResult = WhiteWins;
10117 else if( result < 0 ) {
10118 gameResult = BlackWins;
10121 if( gameMode == TwoMachinesPlay ) {
10122 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10127 // [HGM] save: calculate checksum of game to make games easily identifiable
10129 StringCheckSum (char *s)
10132 if(s==NULL) return 0;
10133 while(*s) i = i*259 + *s++;
10141 for(i=backwardMostMove; i<forwardMostMove; i++) {
10142 sum += pvInfoList[i].depth;
10143 sum += StringCheckSum(parseList[i]);
10144 sum += StringCheckSum(commentList[i]);
10147 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10148 return sum + StringCheckSum(commentList[i]);
10149 } // end of save patch
10152 GameEnds (ChessMove result, char *resultDetails, int whosays)
10154 GameMode nextGameMode;
10156 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10158 if(endingGame) return; /* [HGM] crash: forbid recursion */
10160 if(twoBoards) { // [HGM] dual: switch back to one board
10161 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10162 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10164 if (appData.debugMode) {
10165 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10166 result, resultDetails ? resultDetails : "(null)", whosays);
10169 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10171 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10172 /* If we are playing on ICS, the server decides when the
10173 game is over, but the engine can offer to draw, claim
10177 if (appData.zippyPlay && first.initDone) {
10178 if (result == GameIsDrawn) {
10179 /* In case draw still needs to be claimed */
10180 SendToICS(ics_prefix);
10181 SendToICS("draw\n");
10182 } else if (StrCaseStr(resultDetails, "resign")) {
10183 SendToICS(ics_prefix);
10184 SendToICS("resign\n");
10188 endingGame = 0; /* [HGM] crash */
10192 /* If we're loading the game from a file, stop */
10193 if (whosays == GE_FILE) {
10194 (void) StopLoadGameTimer();
10198 /* Cancel draw offers */
10199 first.offeredDraw = second.offeredDraw = 0;
10201 /* If this is an ICS game, only ICS can really say it's done;
10202 if not, anyone can. */
10203 isIcsGame = (gameMode == IcsPlayingWhite ||
10204 gameMode == IcsPlayingBlack ||
10205 gameMode == IcsObserving ||
10206 gameMode == IcsExamining);
10208 if (!isIcsGame || whosays == GE_ICS) {
10209 /* OK -- not an ICS game, or ICS said it was done */
10211 if (!isIcsGame && !appData.noChessProgram)
10212 SetUserThinkingEnables();
10214 /* [HGM] if a machine claims the game end we verify this claim */
10215 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10216 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10218 ChessMove trueResult = (ChessMove) -1;
10220 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10221 first.twoMachinesColor[0] :
10222 second.twoMachinesColor[0] ;
10224 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10225 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10226 /* [HGM] verify: engine mate claims accepted if they were flagged */
10227 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10229 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10230 /* [HGM] verify: engine mate claims accepted if they were flagged */
10231 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10233 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10234 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10237 // now verify win claims, but not in drop games, as we don't understand those yet
10238 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10239 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10240 (result == WhiteWins && claimer == 'w' ||
10241 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10242 if (appData.debugMode) {
10243 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10244 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10246 if(result != trueResult) {
10247 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10248 result = claimer == 'w' ? BlackWins : WhiteWins;
10249 resultDetails = buf;
10252 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10253 && (forwardMostMove <= backwardMostMove ||
10254 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10255 (claimer=='b')==(forwardMostMove&1))
10257 /* [HGM] verify: draws that were not flagged are false claims */
10258 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10259 result = claimer == 'w' ? BlackWins : WhiteWins;
10260 resultDetails = buf;
10262 /* (Claiming a loss is accepted no questions asked!) */
10264 /* [HGM] bare: don't allow bare King to win */
10265 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10266 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10267 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10268 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10269 && result != GameIsDrawn)
10270 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10271 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10272 int p = (signed char)boards[forwardMostMove][i][j] - color;
10273 if(p >= 0 && p <= (int)WhiteKing) k++;
10275 if (appData.debugMode) {
10276 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10277 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10280 result = GameIsDrawn;
10281 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10282 resultDetails = buf;
10288 if(serverMoves != NULL && !loadFlag) { char c = '=';
10289 if(result==WhiteWins) c = '+';
10290 if(result==BlackWins) c = '-';
10291 if(resultDetails != NULL)
10292 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10294 if (resultDetails != NULL) {
10295 gameInfo.result = result;
10296 gameInfo.resultDetails = StrSave(resultDetails);
10298 /* display last move only if game was not loaded from file */
10299 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10300 DisplayMove(currentMove - 1);
10302 if (forwardMostMove != 0) {
10303 if (gameMode != PlayFromGameFile && gameMode != EditGame
10304 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10306 if (*appData.saveGameFile != NULLCHAR) {
10307 SaveGameToFile(appData.saveGameFile, TRUE);
10308 } else if (appData.autoSaveGames) {
10311 if (*appData.savePositionFile != NULLCHAR) {
10312 SavePositionToFile(appData.savePositionFile);
10317 /* Tell program how game ended in case it is learning */
10318 /* [HGM] Moved this to after saving the PGN, just in case */
10319 /* engine died and we got here through time loss. In that */
10320 /* case we will get a fatal error writing the pipe, which */
10321 /* would otherwise lose us the PGN. */
10322 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10323 /* output during GameEnds should never be fatal anymore */
10324 if (gameMode == MachinePlaysWhite ||
10325 gameMode == MachinePlaysBlack ||
10326 gameMode == TwoMachinesPlay ||
10327 gameMode == IcsPlayingWhite ||
10328 gameMode == IcsPlayingBlack ||
10329 gameMode == BeginningOfGame) {
10331 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10333 if (first.pr != NoProc) {
10334 SendToProgram(buf, &first);
10336 if (second.pr != NoProc &&
10337 gameMode == TwoMachinesPlay) {
10338 SendToProgram(buf, &second);
10343 if (appData.icsActive) {
10344 if (appData.quietPlay &&
10345 (gameMode == IcsPlayingWhite ||
10346 gameMode == IcsPlayingBlack)) {
10347 SendToICS(ics_prefix);
10348 SendToICS("set shout 1\n");
10350 nextGameMode = IcsIdle;
10351 ics_user_moved = FALSE;
10352 /* clean up premove. It's ugly when the game has ended and the
10353 * premove highlights are still on the board.
10356 gotPremove = FALSE;
10357 ClearPremoveHighlights();
10358 DrawPosition(FALSE, boards[currentMove]);
10360 if (whosays == GE_ICS) {
10363 if (gameMode == IcsPlayingWhite)
10365 else if(gameMode == IcsPlayingBlack)
10366 PlayIcsLossSound();
10369 if (gameMode == IcsPlayingBlack)
10371 else if(gameMode == IcsPlayingWhite)
10372 PlayIcsLossSound();
10375 PlayIcsDrawSound();
10378 PlayIcsUnfinishedSound();
10381 } else if (gameMode == EditGame ||
10382 gameMode == PlayFromGameFile ||
10383 gameMode == AnalyzeMode ||
10384 gameMode == AnalyzeFile) {
10385 nextGameMode = gameMode;
10387 nextGameMode = EndOfGame;
10392 nextGameMode = gameMode;
10395 if (appData.noChessProgram) {
10396 gameMode = nextGameMode;
10398 endingGame = 0; /* [HGM] crash */
10403 /* Put first chess program into idle state */
10404 if (first.pr != NoProc &&
10405 (gameMode == MachinePlaysWhite ||
10406 gameMode == MachinePlaysBlack ||
10407 gameMode == TwoMachinesPlay ||
10408 gameMode == IcsPlayingWhite ||
10409 gameMode == IcsPlayingBlack ||
10410 gameMode == BeginningOfGame)) {
10411 SendToProgram("force\n", &first);
10412 if (first.usePing) {
10414 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10415 SendToProgram(buf, &first);
10418 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10419 /* Kill off first chess program */
10420 if (first.isr != NULL)
10421 RemoveInputSource(first.isr);
10424 if (first.pr != NoProc) {
10426 DoSleep( appData.delayBeforeQuit );
10427 SendToProgram("quit\n", &first);
10428 DoSleep( appData.delayAfterQuit );
10429 DestroyChildProcess(first.pr, first.useSigterm);
10433 if (second.reuse) {
10434 /* Put second chess program into idle state */
10435 if (second.pr != NoProc &&
10436 gameMode == TwoMachinesPlay) {
10437 SendToProgram("force\n", &second);
10438 if (second.usePing) {
10440 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10441 SendToProgram(buf, &second);
10444 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10445 /* Kill off second chess program */
10446 if (second.isr != NULL)
10447 RemoveInputSource(second.isr);
10450 if (second.pr != NoProc) {
10451 DoSleep( appData.delayBeforeQuit );
10452 SendToProgram("quit\n", &second);
10453 DoSleep( appData.delayAfterQuit );
10454 DestroyChildProcess(second.pr, second.useSigterm);
10456 second.pr = NoProc;
10459 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10460 char resChar = '=';
10464 if (first.twoMachinesColor[0] == 'w') {
10467 second.matchWins++;
10472 if (first.twoMachinesColor[0] == 'b') {
10475 second.matchWins++;
10478 case GameUnfinished:
10484 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10485 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10486 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10487 ReserveGame(nextGame, resChar); // sets nextGame
10488 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10489 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10490 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10492 if (nextGame <= appData.matchGames && !abortMatch) {
10493 gameMode = nextGameMode;
10494 matchGame = nextGame; // this will be overruled in tourney mode!
10495 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10496 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10497 endingGame = 0; /* [HGM] crash */
10500 gameMode = nextGameMode;
10501 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10502 first.tidy, second.tidy,
10503 first.matchWins, second.matchWins,
10504 appData.matchGames - (first.matchWins + second.matchWins));
10505 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10506 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10507 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10508 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10509 first.twoMachinesColor = "black\n";
10510 second.twoMachinesColor = "white\n";
10512 first.twoMachinesColor = "white\n";
10513 second.twoMachinesColor = "black\n";
10517 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10518 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10520 gameMode = nextGameMode;
10522 endingGame = 0; /* [HGM] crash */
10523 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10524 if(matchMode == TRUE) { // match through command line: exit with or without popup
10526 ToNrEvent(forwardMostMove);
10527 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10529 } else DisplayFatalError(buf, 0, 0);
10530 } else { // match through menu; just stop, with or without popup
10531 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10534 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10535 } else DisplayNote(buf);
10537 if(ranking) free(ranking);
10541 /* Assumes program was just initialized (initString sent).
10542 Leaves program in force mode. */
10544 FeedMovesToProgram (ChessProgramState *cps, int upto)
10548 if (appData.debugMode)
10549 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10550 startedFromSetupPosition ? "position and " : "",
10551 backwardMostMove, upto, cps->which);
10552 if(currentlyInitializedVariant != gameInfo.variant) {
10554 // [HGM] variantswitch: make engine aware of new variant
10555 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10556 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10557 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10558 SendToProgram(buf, cps);
10559 currentlyInitializedVariant = gameInfo.variant;
10561 SendToProgram("force\n", cps);
10562 if (startedFromSetupPosition) {
10563 SendBoard(cps, backwardMostMove);
10564 if (appData.debugMode) {
10565 fprintf(debugFP, "feedMoves\n");
10568 for (i = backwardMostMove; i < upto; i++) {
10569 SendMoveToProgram(i, cps);
10575 ResurrectChessProgram ()
10577 /* The chess program may have exited.
10578 If so, restart it and feed it all the moves made so far. */
10579 static int doInit = 0;
10581 if (appData.noChessProgram) return 1;
10583 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10584 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10585 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10586 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10588 if (first.pr != NoProc) return 1;
10589 StartChessProgram(&first);
10591 InitChessProgram(&first, FALSE);
10592 FeedMovesToProgram(&first, currentMove);
10594 if (!first.sendTime) {
10595 /* can't tell gnuchess what its clock should read,
10596 so we bow to its notion. */
10598 timeRemaining[0][currentMove] = whiteTimeRemaining;
10599 timeRemaining[1][currentMove] = blackTimeRemaining;
10602 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10603 appData.icsEngineAnalyze) && first.analysisSupport) {
10604 SendToProgram("analyze\n", &first);
10605 first.analyzing = TRUE;
10611 * Button procedures
10614 Reset (int redraw, int init)
10618 if (appData.debugMode) {
10619 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10620 redraw, init, gameMode);
10622 CleanupTail(); // [HGM] vari: delete any stored variations
10623 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10624 pausing = pauseExamInvalid = FALSE;
10625 startedFromSetupPosition = blackPlaysFirst = FALSE;
10627 whiteFlag = blackFlag = FALSE;
10628 userOfferedDraw = FALSE;
10629 hintRequested = bookRequested = FALSE;
10630 first.maybeThinking = FALSE;
10631 second.maybeThinking = FALSE;
10632 first.bookSuspend = FALSE; // [HGM] book
10633 second.bookSuspend = FALSE;
10634 thinkOutput[0] = NULLCHAR;
10635 lastHint[0] = NULLCHAR;
10636 ClearGameInfo(&gameInfo);
10637 gameInfo.variant = StringToVariant(appData.variant);
10638 ics_user_moved = ics_clock_paused = FALSE;
10639 ics_getting_history = H_FALSE;
10641 white_holding[0] = black_holding[0] = NULLCHAR;
10642 ClearProgramStats();
10643 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10647 flipView = appData.flipView;
10648 ClearPremoveHighlights();
10649 gotPremove = FALSE;
10650 alarmSounded = FALSE;
10652 GameEnds(EndOfFile, NULL, GE_PLAYER);
10653 if(appData.serverMovesName != NULL) {
10654 /* [HGM] prepare to make moves file for broadcasting */
10655 clock_t t = clock();
10656 if(serverMoves != NULL) fclose(serverMoves);
10657 serverMoves = fopen(appData.serverMovesName, "r");
10658 if(serverMoves != NULL) {
10659 fclose(serverMoves);
10660 /* delay 15 sec before overwriting, so all clients can see end */
10661 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10663 serverMoves = fopen(appData.serverMovesName, "w");
10667 gameMode = BeginningOfGame;
10669 if(appData.icsActive) gameInfo.variant = VariantNormal;
10670 currentMove = forwardMostMove = backwardMostMove = 0;
10671 MarkTargetSquares(1);
10672 InitPosition(redraw);
10673 for (i = 0; i < MAX_MOVES; i++) {
10674 if (commentList[i] != NULL) {
10675 free(commentList[i]);
10676 commentList[i] = NULL;
10680 timeRemaining[0][0] = whiteTimeRemaining;
10681 timeRemaining[1][0] = blackTimeRemaining;
10683 if (first.pr == NoProc) {
10684 StartChessProgram(&first);
10687 InitChessProgram(&first, startedFromSetupPosition);
10690 DisplayMessage("", "");
10691 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10692 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10696 AutoPlayGameLoop ()
10699 if (!AutoPlayOneMove())
10701 if (matchMode || appData.timeDelay == 0)
10703 if (appData.timeDelay < 0)
10705 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10714 int fromX, fromY, toX, toY;
10716 if (appData.debugMode) {
10717 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10720 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10723 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10724 pvInfoList[currentMove].depth = programStats.depth;
10725 pvInfoList[currentMove].score = programStats.score;
10726 pvInfoList[currentMove].time = 0;
10727 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10730 if (currentMove >= forwardMostMove) {
10731 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10732 // gameMode = EndOfGame;
10733 // ModeHighlight();
10735 /* [AS] Clear current move marker at the end of a game */
10736 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10741 toX = moveList[currentMove][2] - AAA;
10742 toY = moveList[currentMove][3] - ONE;
10744 if (moveList[currentMove][1] == '@') {
10745 if (appData.highlightLastMove) {
10746 SetHighlights(-1, -1, toX, toY);
10749 fromX = moveList[currentMove][0] - AAA;
10750 fromY = moveList[currentMove][1] - ONE;
10752 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10754 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10756 if (appData.highlightLastMove) {
10757 SetHighlights(fromX, fromY, toX, toY);
10760 DisplayMove(currentMove);
10761 SendMoveToProgram(currentMove++, &first);
10762 DisplayBothClocks();
10763 DrawPosition(FALSE, boards[currentMove]);
10764 // [HGM] PV info: always display, routine tests if empty
10765 DisplayComment(currentMove - 1, commentList[currentMove]);
10771 LoadGameOneMove (ChessMove readAhead)
10773 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10774 char promoChar = NULLCHAR;
10775 ChessMove moveType;
10776 char move[MSG_SIZ];
10779 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10780 gameMode != AnalyzeMode && gameMode != Training) {
10785 yyboardindex = forwardMostMove;
10786 if (readAhead != EndOfFile) {
10787 moveType = readAhead;
10789 if (gameFileFP == NULL)
10791 moveType = (ChessMove) Myylex();
10795 switch (moveType) {
10797 if (appData.debugMode)
10798 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10801 /* append the comment but don't display it */
10802 AppendComment(currentMove, p, FALSE);
10805 case WhiteCapturesEnPassant:
10806 case BlackCapturesEnPassant:
10807 case WhitePromotion:
10808 case BlackPromotion:
10809 case WhiteNonPromotion:
10810 case BlackNonPromotion:
10812 case WhiteKingSideCastle:
10813 case WhiteQueenSideCastle:
10814 case BlackKingSideCastle:
10815 case BlackQueenSideCastle:
10816 case WhiteKingSideCastleWild:
10817 case WhiteQueenSideCastleWild:
10818 case BlackKingSideCastleWild:
10819 case BlackQueenSideCastleWild:
10821 case WhiteHSideCastleFR:
10822 case WhiteASideCastleFR:
10823 case BlackHSideCastleFR:
10824 case BlackASideCastleFR:
10826 if (appData.debugMode)
10827 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10828 fromX = currentMoveString[0] - AAA;
10829 fromY = currentMoveString[1] - ONE;
10830 toX = currentMoveString[2] - AAA;
10831 toY = currentMoveString[3] - ONE;
10832 promoChar = currentMoveString[4];
10837 if (appData.debugMode)
10838 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10839 fromX = moveType == WhiteDrop ?
10840 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10841 (int) CharToPiece(ToLower(currentMoveString[0]));
10843 toX = currentMoveString[2] - AAA;
10844 toY = currentMoveString[3] - ONE;
10850 case GameUnfinished:
10851 if (appData.debugMode)
10852 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10853 p = strchr(yy_text, '{');
10854 if (p == NULL) p = strchr(yy_text, '(');
10857 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10859 q = strchr(p, *p == '{' ? '}' : ')');
10860 if (q != NULL) *q = NULLCHAR;
10863 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10864 GameEnds(moveType, p, GE_FILE);
10866 if (cmailMsgLoaded) {
10868 flipView = WhiteOnMove(currentMove);
10869 if (moveType == GameUnfinished) flipView = !flipView;
10870 if (appData.debugMode)
10871 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10876 if (appData.debugMode)
10877 fprintf(debugFP, "Parser hit end of file\n");
10878 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10884 if (WhiteOnMove(currentMove)) {
10885 GameEnds(BlackWins, "Black mates", GE_FILE);
10887 GameEnds(WhiteWins, "White mates", GE_FILE);
10891 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10897 case MoveNumberOne:
10898 if (lastLoadGameStart == GNUChessGame) {
10899 /* GNUChessGames have numbers, but they aren't move numbers */
10900 if (appData.debugMode)
10901 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10902 yy_text, (int) moveType);
10903 return LoadGameOneMove(EndOfFile); /* tail recursion */
10905 /* else fall thru */
10910 /* Reached start of next game in file */
10911 if (appData.debugMode)
10912 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10913 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10919 if (WhiteOnMove(currentMove)) {
10920 GameEnds(BlackWins, "Black mates", GE_FILE);
10922 GameEnds(WhiteWins, "White mates", GE_FILE);
10926 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10932 case PositionDiagram: /* should not happen; ignore */
10933 case ElapsedTime: /* ignore */
10934 case NAG: /* ignore */
10935 if (appData.debugMode)
10936 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10937 yy_text, (int) moveType);
10938 return LoadGameOneMove(EndOfFile); /* tail recursion */
10941 if (appData.testLegality) {
10942 if (appData.debugMode)
10943 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10944 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10945 (forwardMostMove / 2) + 1,
10946 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947 DisplayError(move, 0);
10950 if (appData.debugMode)
10951 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10952 yy_text, currentMoveString);
10953 fromX = currentMoveString[0] - AAA;
10954 fromY = currentMoveString[1] - ONE;
10955 toX = currentMoveString[2] - AAA;
10956 toY = currentMoveString[3] - ONE;
10957 promoChar = currentMoveString[4];
10961 case AmbiguousMove:
10962 if (appData.debugMode)
10963 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10964 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10965 (forwardMostMove / 2) + 1,
10966 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10967 DisplayError(move, 0);
10972 case ImpossibleMove:
10973 if (appData.debugMode)
10974 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10975 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10976 (forwardMostMove / 2) + 1,
10977 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10978 DisplayError(move, 0);
10984 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10985 DrawPosition(FALSE, boards[currentMove]);
10986 DisplayBothClocks();
10987 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10988 DisplayComment(currentMove - 1, commentList[currentMove]);
10990 (void) StopLoadGameTimer();
10992 cmailOldMove = forwardMostMove;
10995 /* currentMoveString is set as a side-effect of yylex */
10997 thinkOutput[0] = NULLCHAR;
10998 MakeMove(fromX, fromY, toX, toY, promoChar);
10999 currentMove = forwardMostMove;
11004 /* Load the nth game from the given file */
11006 LoadGameFromFile (char *filename, int n, char *title, int useList)
11011 if (strcmp(filename, "-") == 0) {
11015 f = fopen(filename, "rb");
11017 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11018 DisplayError(buf, errno);
11022 if (fseek(f, 0, 0) == -1) {
11023 /* f is not seekable; probably a pipe */
11026 if (useList && n == 0) {
11027 int error = GameListBuild(f);
11029 DisplayError(_("Cannot build game list"), error);
11030 } else if (!ListEmpty(&gameList) &&
11031 ((ListGame *) gameList.tailPred)->number > 1) {
11032 GameListPopUp(f, title);
11039 return LoadGame(f, n, title, FALSE);
11044 MakeRegisteredMove ()
11046 int fromX, fromY, toX, toY;
11048 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11049 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11052 if (appData.debugMode)
11053 fprintf(debugFP, "Restoring %s for game %d\n",
11054 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11056 thinkOutput[0] = NULLCHAR;
11057 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11058 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11059 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11060 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11061 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11062 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11063 MakeMove(fromX, fromY, toX, toY, promoChar);
11064 ShowMove(fromX, fromY, toX, toY);
11066 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11073 if (WhiteOnMove(currentMove)) {
11074 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11076 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11081 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11088 if (WhiteOnMove(currentMove)) {
11089 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11091 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11096 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11107 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11109 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11113 if (gameNumber > nCmailGames) {
11114 DisplayError(_("No more games in this message"), 0);
11117 if (f == lastLoadGameFP) {
11118 int offset = gameNumber - lastLoadGameNumber;
11120 cmailMsg[0] = NULLCHAR;
11121 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11122 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11123 nCmailMovesRegistered--;
11125 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11126 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11127 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11130 if (! RegisterMove()) return FALSE;
11134 retVal = LoadGame(f, gameNumber, title, useList);
11136 /* Make move registered during previous look at this game, if any */
11137 MakeRegisteredMove();
11139 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11140 commentList[currentMove]
11141 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11142 DisplayComment(currentMove - 1, commentList[currentMove]);
11148 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11150 ReloadGame (int offset)
11152 int gameNumber = lastLoadGameNumber + offset;
11153 if (lastLoadGameFP == NULL) {
11154 DisplayError(_("No game has been loaded yet"), 0);
11157 if (gameNumber <= 0) {
11158 DisplayError(_("Can't back up any further"), 0);
11161 if (cmailMsgLoaded) {
11162 return CmailLoadGame(lastLoadGameFP, gameNumber,
11163 lastLoadGameTitle, lastLoadGameUseList);
11165 return LoadGame(lastLoadGameFP, gameNumber,
11166 lastLoadGameTitle, lastLoadGameUseList);
11170 int keys[EmptySquare+1];
11173 PositionMatches (Board b1, Board b2)
11176 switch(appData.searchMode) {
11177 case 1: return CompareWithRights(b1, b2);
11179 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11180 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11184 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11185 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11186 sum += keys[b1[r][f]] - keys[b2[r][f]];
11190 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11191 sum += keys[b1[r][f]] - keys[b2[r][f]];
11203 int pieceList[256], quickBoard[256];
11204 ChessSquare pieceType[256] = { EmptySquare };
11205 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11206 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11207 int soughtTotal, turn;
11208 Boolean epOK, flipSearch;
11211 unsigned char piece, to;
11214 #define DSIZE (250000)
11216 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11217 Move *moveDatabase = initialSpace;
11218 unsigned int movePtr, dataSize = DSIZE;
11221 MakePieceList (Board board, int *counts)
11223 int r, f, n=Q_PROMO, total=0;
11224 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11225 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11226 int sq = f + (r<<4);
11227 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11228 quickBoard[sq] = ++n;
11230 pieceType[n] = board[r][f];
11231 counts[board[r][f]]++;
11232 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11233 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11237 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11242 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11244 int sq = fromX + (fromY<<4);
11245 int piece = quickBoard[sq];
11246 quickBoard[sq] = 0;
11247 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11248 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11249 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11250 moveDatabase[movePtr++].piece = Q_WCASTL;
11251 quickBoard[sq] = piece;
11252 piece = quickBoard[from]; quickBoard[from] = 0;
11253 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11255 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11256 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11257 moveDatabase[movePtr++].piece = Q_BCASTL;
11258 quickBoard[sq] = piece;
11259 piece = quickBoard[from]; quickBoard[from] = 0;
11260 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11262 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11263 quickBoard[(fromY<<4)+toX] = 0;
11264 moveDatabase[movePtr].piece = Q_EP;
11265 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11266 moveDatabase[movePtr].to = sq;
11268 if(promoPiece != pieceType[piece]) {
11269 moveDatabase[movePtr++].piece = Q_PROMO;
11270 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11272 moveDatabase[movePtr].piece = piece;
11273 quickBoard[sq] = piece;
11278 PackGame (Board board)
11280 Move *newSpace = NULL;
11281 moveDatabase[movePtr].piece = 0; // terminate previous game
11282 if(movePtr > dataSize) {
11283 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11284 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11285 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11288 Move *p = moveDatabase, *q = newSpace;
11289 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11290 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11291 moveDatabase = newSpace;
11292 } else { // calloc failed, we must be out of memory. Too bad...
11293 dataSize = 0; // prevent calloc events for all subsequent games
11294 return 0; // and signal this one isn't cached
11298 MakePieceList(board, counts);
11303 QuickCompare (Board board, int *minCounts, int *maxCounts)
11304 { // compare according to search mode
11306 switch(appData.searchMode)
11308 case 1: // exact position match
11309 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11310 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11311 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11314 case 2: // can have extra material on empty squares
11315 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11316 if(board[r][f] == EmptySquare) continue;
11317 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11320 case 3: // material with exact Pawn structure
11321 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11322 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11323 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11324 } // fall through to material comparison
11325 case 4: // exact material
11326 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11328 case 6: // material range with given imbalance
11329 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11330 // fall through to range comparison
11331 case 5: // material range
11332 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11338 QuickScan (Board board, Move *move)
11339 { // reconstruct game,and compare all positions in it
11340 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11342 int piece = move->piece;
11343 int to = move->to, from = pieceList[piece];
11344 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11345 if(!piece) return -1;
11346 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11347 piece = (++move)->piece;
11348 from = pieceList[piece];
11349 counts[pieceType[piece]]--;
11350 pieceType[piece] = (ChessSquare) move->to;
11351 counts[move->to]++;
11352 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11353 counts[pieceType[quickBoard[to]]]--;
11354 quickBoard[to] = 0; total--;
11357 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11358 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11359 from = pieceList[piece]; // so this must be King
11360 quickBoard[from] = 0;
11361 quickBoard[to] = piece;
11362 pieceList[piece] = to;
11367 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11368 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11369 quickBoard[from] = 0;
11370 quickBoard[to] = piece;
11371 pieceList[piece] = to;
11373 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11374 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11375 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11376 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11378 static int lastCounts[EmptySquare+1];
11380 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11381 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11382 } else stretch = 0;
11383 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11392 flipSearch = FALSE;
11393 CopyBoard(soughtBoard, boards[currentMove]);
11394 soughtTotal = MakePieceList(soughtBoard, maxSought);
11395 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11396 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11397 CopyBoard(reverseBoard, boards[currentMove]);
11398 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11399 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11400 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11401 reverseBoard[r][f] = piece;
11403 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11404 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11405 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11406 || (boards[currentMove][CASTLING][2] == NoRights ||
11407 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11408 && (boards[currentMove][CASTLING][5] == NoRights ||
11409 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11412 CopyBoard(flipBoard, soughtBoard);
11413 CopyBoard(rotateBoard, reverseBoard);
11414 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11415 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11416 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11419 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11420 if(appData.searchMode >= 5) {
11421 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11422 MakePieceList(soughtBoard, minSought);
11423 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11425 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11426 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11429 GameInfo dummyInfo;
11432 GameContainsPosition (FILE *f, ListGame *lg)
11434 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11435 int fromX, fromY, toX, toY;
11437 static int initDone=FALSE;
11439 // weed out games based on numerical tag comparison
11440 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11441 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11442 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11443 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11445 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11448 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11449 else CopyBoard(boards[scratch], initialPosition); // default start position
11452 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11453 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11456 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11457 fseek(f, lg->offset, 0);
11460 yyboardindex = scratch;
11461 quickFlag = plyNr+1;
11466 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11472 if(plyNr) return -1; // after we have seen moves, this is for new game
11475 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11476 case ImpossibleMove:
11477 case WhiteWins: // game ends here with these four
11480 case GameUnfinished:
11484 if(appData.testLegality) return -1;
11485 case WhiteCapturesEnPassant:
11486 case BlackCapturesEnPassant:
11487 case WhitePromotion:
11488 case BlackPromotion:
11489 case WhiteNonPromotion:
11490 case BlackNonPromotion:
11492 case WhiteKingSideCastle:
11493 case WhiteQueenSideCastle:
11494 case BlackKingSideCastle:
11495 case BlackQueenSideCastle:
11496 case WhiteKingSideCastleWild:
11497 case WhiteQueenSideCastleWild:
11498 case BlackKingSideCastleWild:
11499 case BlackQueenSideCastleWild:
11500 case WhiteHSideCastleFR:
11501 case WhiteASideCastleFR:
11502 case BlackHSideCastleFR:
11503 case BlackASideCastleFR:
11504 fromX = currentMoveString[0] - AAA;
11505 fromY = currentMoveString[1] - ONE;
11506 toX = currentMoveString[2] - AAA;
11507 toY = currentMoveString[3] - ONE;
11508 promoChar = currentMoveString[4];
11512 fromX = next == WhiteDrop ?
11513 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11514 (int) CharToPiece(ToLower(currentMoveString[0]));
11516 toX = currentMoveString[2] - AAA;
11517 toY = currentMoveString[3] - ONE;
11521 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11523 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11524 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11525 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11526 if(appData.findMirror) {
11527 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11528 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11533 /* Load the nth game from open file f */
11535 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11539 int gn = gameNumber;
11540 ListGame *lg = NULL;
11541 int numPGNTags = 0;
11543 GameMode oldGameMode;
11544 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11546 if (appData.debugMode)
11547 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11549 if (gameMode == Training )
11550 SetTrainingModeOff();
11552 oldGameMode = gameMode;
11553 if (gameMode != BeginningOfGame) {
11554 Reset(FALSE, TRUE);
11558 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11559 fclose(lastLoadGameFP);
11563 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11566 fseek(f, lg->offset, 0);
11567 GameListHighlight(gameNumber);
11568 pos = lg->position;
11572 DisplayError(_("Game number out of range"), 0);
11577 if (fseek(f, 0, 0) == -1) {
11578 if (f == lastLoadGameFP ?
11579 gameNumber == lastLoadGameNumber + 1 :
11583 DisplayError(_("Can't seek on game file"), 0);
11588 lastLoadGameFP = f;
11589 lastLoadGameNumber = gameNumber;
11590 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11591 lastLoadGameUseList = useList;
11595 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11596 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11597 lg->gameInfo.black);
11599 } else if (*title != NULLCHAR) {
11600 if (gameNumber > 1) {
11601 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11604 DisplayTitle(title);
11608 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11609 gameMode = PlayFromGameFile;
11613 currentMove = forwardMostMove = backwardMostMove = 0;
11614 CopyBoard(boards[0], initialPosition);
11618 * Skip the first gn-1 games in the file.
11619 * Also skip over anything that precedes an identifiable
11620 * start of game marker, to avoid being confused by
11621 * garbage at the start of the file. Currently
11622 * recognized start of game markers are the move number "1",
11623 * the pattern "gnuchess .* game", the pattern
11624 * "^[#;%] [^ ]* game file", and a PGN tag block.
11625 * A game that starts with one of the latter two patterns
11626 * will also have a move number 1, possibly
11627 * following a position diagram.
11628 * 5-4-02: Let's try being more lenient and allowing a game to
11629 * start with an unnumbered move. Does that break anything?
11631 cm = lastLoadGameStart = EndOfFile;
11633 yyboardindex = forwardMostMove;
11634 cm = (ChessMove) Myylex();
11637 if (cmailMsgLoaded) {
11638 nCmailGames = CMAIL_MAX_GAMES - gn;
11641 DisplayError(_("Game not found in file"), 0);
11648 lastLoadGameStart = cm;
11651 case MoveNumberOne:
11652 switch (lastLoadGameStart) {
11657 case MoveNumberOne:
11659 gn--; /* count this game */
11660 lastLoadGameStart = cm;
11669 switch (lastLoadGameStart) {
11672 case MoveNumberOne:
11674 gn--; /* count this game */
11675 lastLoadGameStart = cm;
11678 lastLoadGameStart = cm; /* game counted already */
11686 yyboardindex = forwardMostMove;
11687 cm = (ChessMove) Myylex();
11688 } while (cm == PGNTag || cm == Comment);
11695 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11696 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11697 != CMAIL_OLD_RESULT) {
11699 cmailResult[ CMAIL_MAX_GAMES
11700 - gn - 1] = CMAIL_OLD_RESULT;
11706 /* Only a NormalMove can be at the start of a game
11707 * without a position diagram. */
11708 if (lastLoadGameStart == EndOfFile ) {
11710 lastLoadGameStart = MoveNumberOne;
11719 if (appData.debugMode)
11720 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11722 if (cm == XBoardGame) {
11723 /* Skip any header junk before position diagram and/or move 1 */
11725 yyboardindex = forwardMostMove;
11726 cm = (ChessMove) Myylex();
11728 if (cm == EndOfFile ||
11729 cm == GNUChessGame || cm == XBoardGame) {
11730 /* Empty game; pretend end-of-file and handle later */
11735 if (cm == MoveNumberOne || cm == PositionDiagram ||
11736 cm == PGNTag || cm == Comment)
11739 } else if (cm == GNUChessGame) {
11740 if (gameInfo.event != NULL) {
11741 free(gameInfo.event);
11743 gameInfo.event = StrSave(yy_text);
11746 startedFromSetupPosition = FALSE;
11747 while (cm == PGNTag) {
11748 if (appData.debugMode)
11749 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11750 err = ParsePGNTag(yy_text, &gameInfo);
11751 if (!err) numPGNTags++;
11753 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11754 if(gameInfo.variant != oldVariant) {
11755 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11756 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11757 InitPosition(TRUE);
11758 oldVariant = gameInfo.variant;
11759 if (appData.debugMode)
11760 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11764 if (gameInfo.fen != NULL) {
11765 Board initial_position;
11766 startedFromSetupPosition = TRUE;
11767 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11769 DisplayError(_("Bad FEN position in file"), 0);
11772 CopyBoard(boards[0], initial_position);
11773 if (blackPlaysFirst) {
11774 currentMove = forwardMostMove = backwardMostMove = 1;
11775 CopyBoard(boards[1], initial_position);
11776 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11777 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11778 timeRemaining[0][1] = whiteTimeRemaining;
11779 timeRemaining[1][1] = blackTimeRemaining;
11780 if (commentList[0] != NULL) {
11781 commentList[1] = commentList[0];
11782 commentList[0] = NULL;
11785 currentMove = forwardMostMove = backwardMostMove = 0;
11787 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11789 initialRulePlies = FENrulePlies;
11790 for( i=0; i< nrCastlingRights; i++ )
11791 initialRights[i] = initial_position[CASTLING][i];
11793 yyboardindex = forwardMostMove;
11794 free(gameInfo.fen);
11795 gameInfo.fen = NULL;
11798 yyboardindex = forwardMostMove;
11799 cm = (ChessMove) Myylex();
11801 /* Handle comments interspersed among the tags */
11802 while (cm == Comment) {
11804 if (appData.debugMode)
11805 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11807 AppendComment(currentMove, p, FALSE);
11808 yyboardindex = forwardMostMove;
11809 cm = (ChessMove) Myylex();
11813 /* don't rely on existence of Event tag since if game was
11814 * pasted from clipboard the Event tag may not exist
11816 if (numPGNTags > 0){
11818 if (gameInfo.variant == VariantNormal) {
11819 VariantClass v = StringToVariant(gameInfo.event);
11820 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11821 if(v < VariantShogi) gameInfo.variant = v;
11824 if( appData.autoDisplayTags ) {
11825 tags = PGNTags(&gameInfo);
11826 TagsPopUp(tags, CmailMsg());
11831 /* Make something up, but don't display it now */
11836 if (cm == PositionDiagram) {
11839 Board initial_position;
11841 if (appData.debugMode)
11842 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11844 if (!startedFromSetupPosition) {
11846 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11847 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11858 initial_position[i][j++] = CharToPiece(*p);
11861 while (*p == ' ' || *p == '\t' ||
11862 *p == '\n' || *p == '\r') p++;
11864 if (strncmp(p, "black", strlen("black"))==0)
11865 blackPlaysFirst = TRUE;
11867 blackPlaysFirst = FALSE;
11868 startedFromSetupPosition = TRUE;
11870 CopyBoard(boards[0], initial_position);
11871 if (blackPlaysFirst) {
11872 currentMove = forwardMostMove = backwardMostMove = 1;
11873 CopyBoard(boards[1], initial_position);
11874 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11875 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11876 timeRemaining[0][1] = whiteTimeRemaining;
11877 timeRemaining[1][1] = blackTimeRemaining;
11878 if (commentList[0] != NULL) {
11879 commentList[1] = commentList[0];
11880 commentList[0] = NULL;
11883 currentMove = forwardMostMove = backwardMostMove = 0;
11886 yyboardindex = forwardMostMove;
11887 cm = (ChessMove) Myylex();
11890 if (first.pr == NoProc) {
11891 StartChessProgram(&first);
11893 InitChessProgram(&first, FALSE);
11894 SendToProgram("force\n", &first);
11895 if (startedFromSetupPosition) {
11896 SendBoard(&first, forwardMostMove);
11897 if (appData.debugMode) {
11898 fprintf(debugFP, "Load Game\n");
11900 DisplayBothClocks();
11903 /* [HGM] server: flag to write setup moves in broadcast file as one */
11904 loadFlag = appData.suppressLoadMoves;
11906 while (cm == Comment) {
11908 if (appData.debugMode)
11909 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11911 AppendComment(currentMove, p, FALSE);
11912 yyboardindex = forwardMostMove;
11913 cm = (ChessMove) Myylex();
11916 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11917 cm == WhiteWins || cm == BlackWins ||
11918 cm == GameIsDrawn || cm == GameUnfinished) {
11919 DisplayMessage("", _("No moves in game"));
11920 if (cmailMsgLoaded) {
11921 if (appData.debugMode)
11922 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11926 DrawPosition(FALSE, boards[currentMove]);
11927 DisplayBothClocks();
11928 gameMode = EditGame;
11935 // [HGM] PV info: routine tests if comment empty
11936 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11937 DisplayComment(currentMove - 1, commentList[currentMove]);
11939 if (!matchMode && appData.timeDelay != 0)
11940 DrawPosition(FALSE, boards[currentMove]);
11942 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11943 programStats.ok_to_send = 1;
11946 /* if the first token after the PGN tags is a move
11947 * and not move number 1, retrieve it from the parser
11949 if (cm != MoveNumberOne)
11950 LoadGameOneMove(cm);
11952 /* load the remaining moves from the file */
11953 while (LoadGameOneMove(EndOfFile)) {
11954 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11955 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11958 /* rewind to the start of the game */
11959 currentMove = backwardMostMove;
11961 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11963 if (oldGameMode == AnalyzeFile ||
11964 oldGameMode == AnalyzeMode) {
11965 AnalyzeFileEvent();
11968 if (!matchMode && pos >= 0) {
11969 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11971 if (matchMode || appData.timeDelay == 0) {
11973 } else if (appData.timeDelay > 0) {
11974 AutoPlayGameLoop();
11977 if (appData.debugMode)
11978 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11980 loadFlag = 0; /* [HGM] true game starts */
11984 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11986 ReloadPosition (int offset)
11988 int positionNumber = lastLoadPositionNumber + offset;
11989 if (lastLoadPositionFP == NULL) {
11990 DisplayError(_("No position has been loaded yet"), 0);
11993 if (positionNumber <= 0) {
11994 DisplayError(_("Can't back up any further"), 0);
11997 return LoadPosition(lastLoadPositionFP, positionNumber,
11998 lastLoadPositionTitle);
12001 /* Load the nth position from the given file */
12003 LoadPositionFromFile (char *filename, int n, char *title)
12008 if (strcmp(filename, "-") == 0) {
12009 return LoadPosition(stdin, n, "stdin");
12011 f = fopen(filename, "rb");
12013 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12014 DisplayError(buf, errno);
12017 return LoadPosition(f, n, title);
12022 /* Load the nth position from the given open file, and close it */
12024 LoadPosition (FILE *f, int positionNumber, char *title)
12026 char *p, line[MSG_SIZ];
12027 Board initial_position;
12028 int i, j, fenMode, pn;
12030 if (gameMode == Training )
12031 SetTrainingModeOff();
12033 if (gameMode != BeginningOfGame) {
12034 Reset(FALSE, TRUE);
12036 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12037 fclose(lastLoadPositionFP);
12039 if (positionNumber == 0) positionNumber = 1;
12040 lastLoadPositionFP = f;
12041 lastLoadPositionNumber = positionNumber;
12042 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12043 if (first.pr == NoProc && !appData.noChessProgram) {
12044 StartChessProgram(&first);
12045 InitChessProgram(&first, FALSE);
12047 pn = positionNumber;
12048 if (positionNumber < 0) {
12049 /* Negative position number means to seek to that byte offset */
12050 if (fseek(f, -positionNumber, 0) == -1) {
12051 DisplayError(_("Can't seek on position file"), 0);
12056 if (fseek(f, 0, 0) == -1) {
12057 if (f == lastLoadPositionFP ?
12058 positionNumber == lastLoadPositionNumber + 1 :
12059 positionNumber == 1) {
12062 DisplayError(_("Can't seek on position file"), 0);
12067 /* See if this file is FEN or old-style xboard */
12068 if (fgets(line, MSG_SIZ, f) == NULL) {
12069 DisplayError(_("Position not found in file"), 0);
12072 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12073 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12076 if (fenMode || line[0] == '#') pn--;
12078 /* skip positions before number pn */
12079 if (fgets(line, MSG_SIZ, f) == NULL) {
12081 DisplayError(_("Position not found in file"), 0);
12084 if (fenMode || line[0] == '#') pn--;
12089 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12090 DisplayError(_("Bad FEN position in file"), 0);
12094 (void) fgets(line, MSG_SIZ, f);
12095 (void) fgets(line, MSG_SIZ, f);
12097 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12098 (void) fgets(line, MSG_SIZ, f);
12099 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12102 initial_position[i][j++] = CharToPiece(*p);
12106 blackPlaysFirst = FALSE;
12108 (void) fgets(line, MSG_SIZ, f);
12109 if (strncmp(line, "black", strlen("black"))==0)
12110 blackPlaysFirst = TRUE;
12113 startedFromSetupPosition = TRUE;
12115 CopyBoard(boards[0], initial_position);
12116 if (blackPlaysFirst) {
12117 currentMove = forwardMostMove = backwardMostMove = 1;
12118 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12119 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12120 CopyBoard(boards[1], initial_position);
12121 DisplayMessage("", _("Black to play"));
12123 currentMove = forwardMostMove = backwardMostMove = 0;
12124 DisplayMessage("", _("White to play"));
12126 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12127 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12128 SendToProgram("force\n", &first);
12129 SendBoard(&first, forwardMostMove);
12131 if (appData.debugMode) {
12133 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12134 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12135 fprintf(debugFP, "Load Position\n");
12138 if (positionNumber > 1) {
12139 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12140 DisplayTitle(line);
12142 DisplayTitle(title);
12144 gameMode = EditGame;
12147 timeRemaining[0][1] = whiteTimeRemaining;
12148 timeRemaining[1][1] = blackTimeRemaining;
12149 DrawPosition(FALSE, boards[currentMove]);
12156 CopyPlayerNameIntoFileName (char **dest, char *src)
12158 while (*src != NULLCHAR && *src != ',') {
12163 *(*dest)++ = *src++;
12169 DefaultFileName (char *ext)
12171 static char def[MSG_SIZ];
12174 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12176 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12178 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12180 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12187 /* Save the current game to the given file */
12189 SaveGameToFile (char *filename, int append)
12193 int result, i, t,tot=0;
12195 if (strcmp(filename, "-") == 0) {
12196 return SaveGame(stdout, 0, NULL);
12198 for(i=0; i<10; i++) { // upto 10 tries
12199 f = fopen(filename, append ? "a" : "w");
12200 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12201 if(f || errno != 13) break;
12202 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12206 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12207 DisplayError(buf, errno);
12210 safeStrCpy(buf, lastMsg, MSG_SIZ);
12211 DisplayMessage(_("Waiting for access to save file"), "");
12212 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12213 DisplayMessage(_("Saving game"), "");
12214 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12215 result = SaveGame(f, 0, NULL);
12216 DisplayMessage(buf, "");
12223 SavePart (char *str)
12225 static char buf[MSG_SIZ];
12228 p = strchr(str, ' ');
12229 if (p == NULL) return str;
12230 strncpy(buf, str, p - str);
12231 buf[p - str] = NULLCHAR;
12235 #define PGN_MAX_LINE 75
12237 #define PGN_SIDE_WHITE 0
12238 #define PGN_SIDE_BLACK 1
12241 FindFirstMoveOutOfBook (int side)
12245 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12246 int index = backwardMostMove;
12247 int has_book_hit = 0;
12249 if( (index % 2) != side ) {
12253 while( index < forwardMostMove ) {
12254 /* Check to see if engine is in book */
12255 int depth = pvInfoList[index].depth;
12256 int score = pvInfoList[index].score;
12262 else if( score == 0 && depth == 63 ) {
12263 in_book = 1; /* Zappa */
12265 else if( score == 2 && depth == 99 ) {
12266 in_book = 1; /* Abrok */
12269 has_book_hit += in_book;
12285 GetOutOfBookInfo (char * buf)
12289 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12291 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12292 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12296 if( oob[0] >= 0 || oob[1] >= 0 ) {
12297 for( i=0; i<2; i++ ) {
12301 if( i > 0 && oob[0] >= 0 ) {
12302 strcat( buf, " " );
12305 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12306 sprintf( buf+strlen(buf), "%s%.2f",
12307 pvInfoList[idx].score >= 0 ? "+" : "",
12308 pvInfoList[idx].score / 100.0 );
12314 /* Save game in PGN style and close the file */
12316 SaveGamePGN (FILE *f)
12318 int i, offset, linelen, newblock;
12322 int movelen, numlen, blank;
12323 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12325 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12327 tm = time((time_t *) NULL);
12329 PrintPGNTags(f, &gameInfo);
12331 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12333 if (backwardMostMove > 0 || startedFromSetupPosition) {
12334 char *fen = PositionToFEN(backwardMostMove, NULL);
12335 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12336 fprintf(f, "\n{--------------\n");
12337 PrintPosition(f, backwardMostMove);
12338 fprintf(f, "--------------}\n");
12342 /* [AS] Out of book annotation */
12343 if( appData.saveOutOfBookInfo ) {
12346 GetOutOfBookInfo( buf );
12348 if( buf[0] != '\0' ) {
12349 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12356 i = backwardMostMove;
12360 while (i < forwardMostMove) {
12361 /* Print comments preceding this move */
12362 if (commentList[i] != NULL) {
12363 if (linelen > 0) fprintf(f, "\n");
12364 fprintf(f, "%s", commentList[i]);
12369 /* Format move number */
12371 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12374 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12376 numtext[0] = NULLCHAR;
12378 numlen = strlen(numtext);
12381 /* Print move number */
12382 blank = linelen > 0 && numlen > 0;
12383 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12392 fprintf(f, "%s", numtext);
12396 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12397 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12400 blank = linelen > 0 && movelen > 0;
12401 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12410 fprintf(f, "%s", move_buffer);
12411 linelen += movelen;
12413 /* [AS] Add PV info if present */
12414 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12415 /* [HGM] add time */
12416 char buf[MSG_SIZ]; int seconds;
12418 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12424 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12427 seconds = (seconds + 4)/10; // round to full seconds
12429 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12431 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12434 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12435 pvInfoList[i].score >= 0 ? "+" : "",
12436 pvInfoList[i].score / 100.0,
12437 pvInfoList[i].depth,
12440 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12442 /* Print score/depth */
12443 blank = linelen > 0 && movelen > 0;
12444 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12453 fprintf(f, "%s", move_buffer);
12454 linelen += movelen;
12460 /* Start a new line */
12461 if (linelen > 0) fprintf(f, "\n");
12463 /* Print comments after last move */
12464 if (commentList[i] != NULL) {
12465 fprintf(f, "%s\n", commentList[i]);
12469 if (gameInfo.resultDetails != NULL &&
12470 gameInfo.resultDetails[0] != NULLCHAR) {
12471 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12472 PGNResult(gameInfo.result));
12474 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12478 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12482 /* Save game in old style and close the file */
12484 SaveGameOldStyle (FILE *f)
12489 tm = time((time_t *) NULL);
12491 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12494 if (backwardMostMove > 0 || startedFromSetupPosition) {
12495 fprintf(f, "\n[--------------\n");
12496 PrintPosition(f, backwardMostMove);
12497 fprintf(f, "--------------]\n");
12502 i = backwardMostMove;
12503 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12505 while (i < forwardMostMove) {
12506 if (commentList[i] != NULL) {
12507 fprintf(f, "[%s]\n", commentList[i]);
12510 if ((i % 2) == 1) {
12511 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12514 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12516 if (commentList[i] != NULL) {
12520 if (i >= forwardMostMove) {
12524 fprintf(f, "%s\n", parseList[i]);
12529 if (commentList[i] != NULL) {
12530 fprintf(f, "[%s]\n", commentList[i]);
12533 /* This isn't really the old style, but it's close enough */
12534 if (gameInfo.resultDetails != NULL &&
12535 gameInfo.resultDetails[0] != NULLCHAR) {
12536 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12537 gameInfo.resultDetails);
12539 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12546 /* Save the current game to open file f and close the file */
12548 SaveGame (FILE *f, int dummy, char *dummy2)
12550 if (gameMode == EditPosition) EditPositionDone(TRUE);
12551 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12552 if (appData.oldSaveStyle)
12553 return SaveGameOldStyle(f);
12555 return SaveGamePGN(f);
12558 /* Save the current position to the given file */
12560 SavePositionToFile (char *filename)
12565 if (strcmp(filename, "-") == 0) {
12566 return SavePosition(stdout, 0, NULL);
12568 f = fopen(filename, "a");
12570 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12571 DisplayError(buf, errno);
12574 safeStrCpy(buf, lastMsg, MSG_SIZ);
12575 DisplayMessage(_("Waiting for access to save file"), "");
12576 flock(fileno(f), LOCK_EX); // [HGM] lock
12577 DisplayMessage(_("Saving position"), "");
12578 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12579 SavePosition(f, 0, NULL);
12580 DisplayMessage(buf, "");
12586 /* Save the current position to the given open file and close the file */
12588 SavePosition (FILE *f, int dummy, char *dummy2)
12593 if (gameMode == EditPosition) EditPositionDone(TRUE);
12594 if (appData.oldSaveStyle) {
12595 tm = time((time_t *) NULL);
12597 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12599 fprintf(f, "[--------------\n");
12600 PrintPosition(f, currentMove);
12601 fprintf(f, "--------------]\n");
12603 fen = PositionToFEN(currentMove, NULL);
12604 fprintf(f, "%s\n", fen);
12612 ReloadCmailMsgEvent (int unregister)
12615 static char *inFilename = NULL;
12616 static char *outFilename;
12618 struct stat inbuf, outbuf;
12621 /* Any registered moves are unregistered if unregister is set, */
12622 /* i.e. invoked by the signal handler */
12624 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12625 cmailMoveRegistered[i] = FALSE;
12626 if (cmailCommentList[i] != NULL) {
12627 free(cmailCommentList[i]);
12628 cmailCommentList[i] = NULL;
12631 nCmailMovesRegistered = 0;
12634 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12635 cmailResult[i] = CMAIL_NOT_RESULT;
12639 if (inFilename == NULL) {
12640 /* Because the filenames are static they only get malloced once */
12641 /* and they never get freed */
12642 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12643 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12645 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12646 sprintf(outFilename, "%s.out", appData.cmailGameName);
12649 status = stat(outFilename, &outbuf);
12651 cmailMailedMove = FALSE;
12653 status = stat(inFilename, &inbuf);
12654 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12657 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12658 counts the games, notes how each one terminated, etc.
12660 It would be nice to remove this kludge and instead gather all
12661 the information while building the game list. (And to keep it
12662 in the game list nodes instead of having a bunch of fixed-size
12663 parallel arrays.) Note this will require getting each game's
12664 termination from the PGN tags, as the game list builder does
12665 not process the game moves. --mann
12667 cmailMsgLoaded = TRUE;
12668 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12670 /* Load first game in the file or popup game menu */
12671 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12673 #endif /* !WIN32 */
12681 char string[MSG_SIZ];
12683 if ( cmailMailedMove
12684 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12685 return TRUE; /* Allow free viewing */
12688 /* Unregister move to ensure that we don't leave RegisterMove */
12689 /* with the move registered when the conditions for registering no */
12691 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12692 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12693 nCmailMovesRegistered --;
12695 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12697 free(cmailCommentList[lastLoadGameNumber - 1]);
12698 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12702 if (cmailOldMove == -1) {
12703 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12707 if (currentMove > cmailOldMove + 1) {
12708 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12712 if (currentMove < cmailOldMove) {
12713 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12717 if (forwardMostMove > currentMove) {
12718 /* Silently truncate extra moves */
12722 if ( (currentMove == cmailOldMove + 1)
12723 || ( (currentMove == cmailOldMove)
12724 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12725 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12726 if (gameInfo.result != GameUnfinished) {
12727 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12730 if (commentList[currentMove] != NULL) {
12731 cmailCommentList[lastLoadGameNumber - 1]
12732 = StrSave(commentList[currentMove]);
12734 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12736 if (appData.debugMode)
12737 fprintf(debugFP, "Saving %s for game %d\n",
12738 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12740 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12742 f = fopen(string, "w");
12743 if (appData.oldSaveStyle) {
12744 SaveGameOldStyle(f); /* also closes the file */
12746 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12747 f = fopen(string, "w");
12748 SavePosition(f, 0, NULL); /* also closes the file */
12750 fprintf(f, "{--------------\n");
12751 PrintPosition(f, currentMove);
12752 fprintf(f, "--------------}\n\n");
12754 SaveGame(f, 0, NULL); /* also closes the file*/
12757 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12758 nCmailMovesRegistered ++;
12759 } else if (nCmailGames == 1) {
12760 DisplayError(_("You have not made a move yet"), 0);
12771 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12772 FILE *commandOutput;
12773 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12774 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12780 if (! cmailMsgLoaded) {
12781 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12785 if (nCmailGames == nCmailResults) {
12786 DisplayError(_("No unfinished games"), 0);
12790 #if CMAIL_PROHIBIT_REMAIL
12791 if (cmailMailedMove) {
12792 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);
12793 DisplayError(msg, 0);
12798 if (! (cmailMailedMove || RegisterMove())) return;
12800 if ( cmailMailedMove
12801 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12802 snprintf(string, MSG_SIZ, partCommandString,
12803 appData.debugMode ? " -v" : "", appData.cmailGameName);
12804 commandOutput = popen(string, "r");
12806 if (commandOutput == NULL) {
12807 DisplayError(_("Failed to invoke cmail"), 0);
12809 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12810 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12812 if (nBuffers > 1) {
12813 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12814 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12815 nBytes = MSG_SIZ - 1;
12817 (void) memcpy(msg, buffer, nBytes);
12819 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12821 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12822 cmailMailedMove = TRUE; /* Prevent >1 moves */
12825 for (i = 0; i < nCmailGames; i ++) {
12826 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12831 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12833 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12835 appData.cmailGameName,
12837 LoadGameFromFile(buffer, 1, buffer, FALSE);
12838 cmailMsgLoaded = FALSE;
12842 DisplayInformation(msg);
12843 pclose(commandOutput);
12846 if ((*cmailMsg) != '\0') {
12847 DisplayInformation(cmailMsg);
12852 #endif /* !WIN32 */
12861 int prependComma = 0;
12863 char string[MSG_SIZ]; /* Space for game-list */
12866 if (!cmailMsgLoaded) return "";
12868 if (cmailMailedMove) {
12869 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12871 /* Create a list of games left */
12872 snprintf(string, MSG_SIZ, "[");
12873 for (i = 0; i < nCmailGames; i ++) {
12874 if (! ( cmailMoveRegistered[i]
12875 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12876 if (prependComma) {
12877 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12879 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12883 strcat(string, number);
12886 strcat(string, "]");
12888 if (nCmailMovesRegistered + nCmailResults == 0) {
12889 switch (nCmailGames) {
12891 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12895 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12899 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12904 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12906 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12911 if (nCmailResults == nCmailGames) {
12912 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12914 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12919 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12931 if (gameMode == Training)
12932 SetTrainingModeOff();
12935 cmailMsgLoaded = FALSE;
12936 if (appData.icsActive) {
12937 SendToICS(ics_prefix);
12938 SendToICS("refresh\n");
12943 ExitEvent (int status)
12947 /* Give up on clean exit */
12951 /* Keep trying for clean exit */
12955 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12957 if (telnetISR != NULL) {
12958 RemoveInputSource(telnetISR);
12960 if (icsPR != NoProc) {
12961 DestroyChildProcess(icsPR, TRUE);
12964 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12965 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12967 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12968 /* make sure this other one finishes before killing it! */
12969 if(endingGame) { int count = 0;
12970 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12971 while(endingGame && count++ < 10) DoSleep(1);
12972 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12975 /* Kill off chess programs */
12976 if (first.pr != NoProc) {
12979 DoSleep( appData.delayBeforeQuit );
12980 SendToProgram("quit\n", &first);
12981 DoSleep( appData.delayAfterQuit );
12982 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12984 if (second.pr != NoProc) {
12985 DoSleep( appData.delayBeforeQuit );
12986 SendToProgram("quit\n", &second);
12987 DoSleep( appData.delayAfterQuit );
12988 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12990 if (first.isr != NULL) {
12991 RemoveInputSource(first.isr);
12993 if (second.isr != NULL) {
12994 RemoveInputSource(second.isr);
12997 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12998 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13000 ShutDownFrontEnd();
13007 if (appData.debugMode)
13008 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13012 if (gameMode == MachinePlaysWhite ||
13013 gameMode == MachinePlaysBlack) {
13016 DisplayBothClocks();
13018 if (gameMode == PlayFromGameFile) {
13019 if (appData.timeDelay >= 0)
13020 AutoPlayGameLoop();
13021 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13022 Reset(FALSE, TRUE);
13023 SendToICS(ics_prefix);
13024 SendToICS("refresh\n");
13025 } else if (currentMove < forwardMostMove) {
13026 ForwardInner(forwardMostMove);
13028 pauseExamInvalid = FALSE;
13030 switch (gameMode) {
13034 pauseExamForwardMostMove = forwardMostMove;
13035 pauseExamInvalid = FALSE;
13038 case IcsPlayingWhite:
13039 case IcsPlayingBlack:
13043 case PlayFromGameFile:
13044 (void) StopLoadGameTimer();
13048 case BeginningOfGame:
13049 if (appData.icsActive) return;
13050 /* else fall through */
13051 case MachinePlaysWhite:
13052 case MachinePlaysBlack:
13053 case TwoMachinesPlay:
13054 if (forwardMostMove == 0)
13055 return; /* don't pause if no one has moved */
13056 if ((gameMode == MachinePlaysWhite &&
13057 !WhiteOnMove(forwardMostMove)) ||
13058 (gameMode == MachinePlaysBlack &&
13059 WhiteOnMove(forwardMostMove))) {
13070 EditCommentEvent ()
13072 char title[MSG_SIZ];
13074 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13075 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13077 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13078 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13079 parseList[currentMove - 1]);
13082 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13089 char *tags = PGNTags(&gameInfo);
13091 EditTagsPopUp(tags, NULL);
13096 AnalyzeModeEvent ()
13098 if (appData.noChessProgram || gameMode == AnalyzeMode)
13101 if (gameMode != AnalyzeFile) {
13102 if (!appData.icsEngineAnalyze) {
13104 if (gameMode != EditGame) return;
13106 ResurrectChessProgram();
13107 SendToProgram("analyze\n", &first);
13108 first.analyzing = TRUE;
13109 /*first.maybeThinking = TRUE;*/
13110 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13111 EngineOutputPopUp();
13113 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13118 StartAnalysisClock();
13119 GetTimeMark(&lastNodeCountTime);
13124 AnalyzeFileEvent ()
13126 if (appData.noChessProgram || gameMode == AnalyzeFile)
13129 if (gameMode != AnalyzeMode) {
13131 if (gameMode != EditGame) return;
13132 ResurrectChessProgram();
13133 SendToProgram("analyze\n", &first);
13134 first.analyzing = TRUE;
13135 /*first.maybeThinking = TRUE;*/
13136 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13137 EngineOutputPopUp();
13139 gameMode = AnalyzeFile;
13144 StartAnalysisClock();
13145 GetTimeMark(&lastNodeCountTime);
13147 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13151 MachineWhiteEvent ()
13154 char *bookHit = NULL;
13156 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13160 if (gameMode == PlayFromGameFile ||
13161 gameMode == TwoMachinesPlay ||
13162 gameMode == Training ||
13163 gameMode == AnalyzeMode ||
13164 gameMode == EndOfGame)
13167 if (gameMode == EditPosition)
13168 EditPositionDone(TRUE);
13170 if (!WhiteOnMove(currentMove)) {
13171 DisplayError(_("It is not White's turn"), 0);
13175 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13178 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13179 gameMode == AnalyzeFile)
13182 ResurrectChessProgram(); /* in case it isn't running */
13183 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13184 gameMode = MachinePlaysWhite;
13187 gameMode = MachinePlaysWhite;
13191 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13193 if (first.sendName) {
13194 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13195 SendToProgram(buf, &first);
13197 if (first.sendTime) {
13198 if (first.useColors) {
13199 SendToProgram("black\n", &first); /*gnu kludge*/
13201 SendTimeRemaining(&first, TRUE);
13203 if (first.useColors) {
13204 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13206 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13207 SetMachineThinkingEnables();
13208 first.maybeThinking = TRUE;
13212 if (appData.autoFlipView && !flipView) {
13213 flipView = !flipView;
13214 DrawPosition(FALSE, NULL);
13215 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13218 if(bookHit) { // [HGM] book: simulate book reply
13219 static char bookMove[MSG_SIZ]; // a bit generous?
13221 programStats.nodes = programStats.depth = programStats.time =
13222 programStats.score = programStats.got_only_move = 0;
13223 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13225 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13226 strcat(bookMove, bookHit);
13227 HandleMachineMove(bookMove, &first);
13232 MachineBlackEvent ()
13235 char *bookHit = NULL;
13237 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13241 if (gameMode == PlayFromGameFile ||
13242 gameMode == TwoMachinesPlay ||
13243 gameMode == Training ||
13244 gameMode == AnalyzeMode ||
13245 gameMode == EndOfGame)
13248 if (gameMode == EditPosition)
13249 EditPositionDone(TRUE);
13251 if (WhiteOnMove(currentMove)) {
13252 DisplayError(_("It is not Black's turn"), 0);
13256 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13259 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13260 gameMode == AnalyzeFile)
13263 ResurrectChessProgram(); /* in case it isn't running */
13264 gameMode = MachinePlaysBlack;
13268 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13270 if (first.sendName) {
13271 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13272 SendToProgram(buf, &first);
13274 if (first.sendTime) {
13275 if (first.useColors) {
13276 SendToProgram("white\n", &first); /*gnu kludge*/
13278 SendTimeRemaining(&first, FALSE);
13280 if (first.useColors) {
13281 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13283 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13284 SetMachineThinkingEnables();
13285 first.maybeThinking = TRUE;
13288 if (appData.autoFlipView && flipView) {
13289 flipView = !flipView;
13290 DrawPosition(FALSE, NULL);
13291 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13293 if(bookHit) { // [HGM] book: simulate book reply
13294 static char bookMove[MSG_SIZ]; // a bit generous?
13296 programStats.nodes = programStats.depth = programStats.time =
13297 programStats.score = programStats.got_only_move = 0;
13298 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13300 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13301 strcat(bookMove, bookHit);
13302 HandleMachineMove(bookMove, &first);
13308 DisplayTwoMachinesTitle ()
13311 if (appData.matchGames > 0) {
13312 if(appData.tourneyFile[0]) {
13313 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13314 gameInfo.white, _("vs."), gameInfo.black,
13315 nextGame+1, appData.matchGames+1,
13316 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13318 if (first.twoMachinesColor[0] == 'w') {
13319 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13320 gameInfo.white, _("vs."), gameInfo.black,
13321 first.matchWins, second.matchWins,
13322 matchGame - 1 - (first.matchWins + second.matchWins));
13324 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13325 gameInfo.white, _("vs."), gameInfo.black,
13326 second.matchWins, first.matchWins,
13327 matchGame - 1 - (first.matchWins + second.matchWins));
13330 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13336 SettingsMenuIfReady ()
13338 if (second.lastPing != second.lastPong) {
13339 DisplayMessage("", _("Waiting for second chess program"));
13340 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13344 DisplayMessage("", "");
13345 SettingsPopUp(&second);
13349 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13352 if (cps->pr == NoProc) {
13353 StartChessProgram(cps);
13354 if (cps->protocolVersion == 1) {
13357 /* kludge: allow timeout for initial "feature" command */
13359 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13360 DisplayMessage("", buf);
13361 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13369 TwoMachinesEvent P((void))
13373 ChessProgramState *onmove;
13374 char *bookHit = NULL;
13375 static int stalling = 0;
13379 if (appData.noChessProgram) return;
13381 switch (gameMode) {
13382 case TwoMachinesPlay:
13384 case MachinePlaysWhite:
13385 case MachinePlaysBlack:
13386 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13387 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13391 case BeginningOfGame:
13392 case PlayFromGameFile:
13395 if (gameMode != EditGame) return;
13398 EditPositionDone(TRUE);
13409 // forwardMostMove = currentMove;
13410 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13412 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13414 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13415 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13416 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13420 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13421 SendToProgram("force\n", &second);
13423 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13426 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13427 if(appData.matchPause>10000 || appData.matchPause<10)
13428 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13429 wait = SubtractTimeMarks(&now, &pauseStart);
13430 if(wait < appData.matchPause) {
13431 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13434 // we are now committed to starting the game
13436 DisplayMessage("", "");
13437 if (startedFromSetupPosition) {
13438 SendBoard(&second, backwardMostMove);
13439 if (appData.debugMode) {
13440 fprintf(debugFP, "Two Machines\n");
13443 for (i = backwardMostMove; i < forwardMostMove; i++) {
13444 SendMoveToProgram(i, &second);
13447 gameMode = TwoMachinesPlay;
13449 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13451 DisplayTwoMachinesTitle();
13453 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13458 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13459 SendToProgram(first.computerString, &first);
13460 if (first.sendName) {
13461 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13462 SendToProgram(buf, &first);
13464 SendToProgram(second.computerString, &second);
13465 if (second.sendName) {
13466 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13467 SendToProgram(buf, &second);
13471 if (!first.sendTime || !second.sendTime) {
13472 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13473 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13475 if (onmove->sendTime) {
13476 if (onmove->useColors) {
13477 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13479 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13481 if (onmove->useColors) {
13482 SendToProgram(onmove->twoMachinesColor, onmove);
13484 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13485 // SendToProgram("go\n", onmove);
13486 onmove->maybeThinking = TRUE;
13487 SetMachineThinkingEnables();
13491 if(bookHit) { // [HGM] book: simulate book reply
13492 static char bookMove[MSG_SIZ]; // a bit generous?
13494 programStats.nodes = programStats.depth = programStats.time =
13495 programStats.score = programStats.got_only_move = 0;
13496 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13498 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13499 strcat(bookMove, bookHit);
13500 savedMessage = bookMove; // args for deferred call
13501 savedState = onmove;
13502 ScheduleDelayedEvent(DeferredBookMove, 1);
13509 if (gameMode == Training) {
13510 SetTrainingModeOff();
13511 gameMode = PlayFromGameFile;
13512 DisplayMessage("", _("Training mode off"));
13514 gameMode = Training;
13515 animateTraining = appData.animate;
13517 /* make sure we are not already at the end of the game */
13518 if (currentMove < forwardMostMove) {
13519 SetTrainingModeOn();
13520 DisplayMessage("", _("Training mode on"));
13522 gameMode = PlayFromGameFile;
13523 DisplayError(_("Already at end of game"), 0);
13532 if (!appData.icsActive) return;
13533 switch (gameMode) {
13534 case IcsPlayingWhite:
13535 case IcsPlayingBlack:
13538 case BeginningOfGame:
13546 EditPositionDone(TRUE);
13559 gameMode = IcsIdle;
13569 switch (gameMode) {
13571 SetTrainingModeOff();
13573 case MachinePlaysWhite:
13574 case MachinePlaysBlack:
13575 case BeginningOfGame:
13576 SendToProgram("force\n", &first);
13577 SetUserThinkingEnables();
13579 case PlayFromGameFile:
13580 (void) StopLoadGameTimer();
13581 if (gameFileFP != NULL) {
13586 EditPositionDone(TRUE);
13591 SendToProgram("force\n", &first);
13593 case TwoMachinesPlay:
13594 GameEnds(EndOfFile, NULL, GE_PLAYER);
13595 ResurrectChessProgram();
13596 SetUserThinkingEnables();
13599 ResurrectChessProgram();
13601 case IcsPlayingBlack:
13602 case IcsPlayingWhite:
13603 DisplayError(_("Warning: You are still playing a game"), 0);
13606 DisplayError(_("Warning: You are still observing a game"), 0);
13609 DisplayError(_("Warning: You are still examining a game"), 0);
13620 first.offeredDraw = second.offeredDraw = 0;
13622 if (gameMode == PlayFromGameFile) {
13623 whiteTimeRemaining = timeRemaining[0][currentMove];
13624 blackTimeRemaining = timeRemaining[1][currentMove];
13628 if (gameMode == MachinePlaysWhite ||
13629 gameMode == MachinePlaysBlack ||
13630 gameMode == TwoMachinesPlay ||
13631 gameMode == EndOfGame) {
13632 i = forwardMostMove;
13633 while (i > currentMove) {
13634 SendToProgram("undo\n", &first);
13637 if(!adjustedClock) {
13638 whiteTimeRemaining = timeRemaining[0][currentMove];
13639 blackTimeRemaining = timeRemaining[1][currentMove];
13640 DisplayBothClocks();
13642 if (whiteFlag || blackFlag) {
13643 whiteFlag = blackFlag = 0;
13648 gameMode = EditGame;
13655 EditPositionEvent ()
13657 if (gameMode == EditPosition) {
13663 if (gameMode != EditGame) return;
13665 gameMode = EditPosition;
13668 if (currentMove > 0)
13669 CopyBoard(boards[0], boards[currentMove]);
13671 blackPlaysFirst = !WhiteOnMove(currentMove);
13673 currentMove = forwardMostMove = backwardMostMove = 0;
13674 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13681 /* [DM] icsEngineAnalyze - possible call from other functions */
13682 if (appData.icsEngineAnalyze) {
13683 appData.icsEngineAnalyze = FALSE;
13685 DisplayMessage("",_("Close ICS engine analyze..."));
13687 if (first.analysisSupport && first.analyzing) {
13688 SendToProgram("exit\n", &first);
13689 first.analyzing = FALSE;
13691 thinkOutput[0] = NULLCHAR;
13695 EditPositionDone (Boolean fakeRights)
13697 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13699 startedFromSetupPosition = TRUE;
13700 InitChessProgram(&first, FALSE);
13701 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13702 boards[0][EP_STATUS] = EP_NONE;
13703 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13704 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13705 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13706 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13707 } else boards[0][CASTLING][2] = NoRights;
13708 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13709 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13710 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13711 } else boards[0][CASTLING][5] = NoRights;
13713 SendToProgram("force\n", &first);
13714 if (blackPlaysFirst) {
13715 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13716 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13717 currentMove = forwardMostMove = backwardMostMove = 1;
13718 CopyBoard(boards[1], boards[0]);
13720 currentMove = forwardMostMove = backwardMostMove = 0;
13722 SendBoard(&first, forwardMostMove);
13723 if (appData.debugMode) {
13724 fprintf(debugFP, "EditPosDone\n");
13727 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13728 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13729 gameMode = EditGame;
13731 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13732 ClearHighlights(); /* [AS] */
13735 /* Pause for `ms' milliseconds */
13736 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13738 TimeDelay (long ms)
13745 } while (SubtractTimeMarks(&m2, &m1) < ms);
13748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13750 SendMultiLineToICS (char *buf)
13752 char temp[MSG_SIZ+1], *p;
13759 strncpy(temp, buf, len);
13764 if (*p == '\n' || *p == '\r')
13769 strcat(temp, "\n");
13771 SendToPlayer(temp, strlen(temp));
13775 SetWhiteToPlayEvent ()
13777 if (gameMode == EditPosition) {
13778 blackPlaysFirst = FALSE;
13779 DisplayBothClocks(); /* works because currentMove is 0 */
13780 } else if (gameMode == IcsExamining) {
13781 SendToICS(ics_prefix);
13782 SendToICS("tomove white\n");
13787 SetBlackToPlayEvent ()
13789 if (gameMode == EditPosition) {
13790 blackPlaysFirst = TRUE;
13791 currentMove = 1; /* kludge */
13792 DisplayBothClocks();
13794 } else if (gameMode == IcsExamining) {
13795 SendToICS(ics_prefix);
13796 SendToICS("tomove black\n");
13801 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13804 ChessSquare piece = boards[0][y][x];
13806 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13808 switch (selection) {
13810 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13811 SendToICS(ics_prefix);
13812 SendToICS("bsetup clear\n");
13813 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13814 SendToICS(ics_prefix);
13815 SendToICS("clearboard\n");
13817 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13818 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13819 for (y = 0; y < BOARD_HEIGHT; y++) {
13820 if (gameMode == IcsExamining) {
13821 if (boards[currentMove][y][x] != EmptySquare) {
13822 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13827 boards[0][y][x] = p;
13832 if (gameMode == EditPosition) {
13833 DrawPosition(FALSE, boards[0]);
13838 SetWhiteToPlayEvent();
13842 SetBlackToPlayEvent();
13846 if (gameMode == IcsExamining) {
13847 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13848 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13851 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13852 if(x == BOARD_LEFT-2) {
13853 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13854 boards[0][y][1] = 0;
13856 if(x == BOARD_RGHT+1) {
13857 if(y >= gameInfo.holdingsSize) break;
13858 boards[0][y][BOARD_WIDTH-2] = 0;
13861 boards[0][y][x] = EmptySquare;
13862 DrawPosition(FALSE, boards[0]);
13867 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13868 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13869 selection = (ChessSquare) (PROMOTED piece);
13870 } else if(piece == EmptySquare) selection = WhiteSilver;
13871 else selection = (ChessSquare)((int)piece - 1);
13875 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13876 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13877 selection = (ChessSquare) (DEMOTED piece);
13878 } else if(piece == EmptySquare) selection = BlackSilver;
13879 else selection = (ChessSquare)((int)piece + 1);
13884 if(gameInfo.variant == VariantShatranj ||
13885 gameInfo.variant == VariantXiangqi ||
13886 gameInfo.variant == VariantCourier ||
13887 gameInfo.variant == VariantMakruk )
13888 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13893 if(gameInfo.variant == VariantXiangqi)
13894 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13895 if(gameInfo.variant == VariantKnightmate)
13896 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13899 if (gameMode == IcsExamining) {
13900 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13901 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13902 PieceToChar(selection), AAA + x, ONE + y);
13905 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13907 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13908 n = PieceToNumber(selection - BlackPawn);
13909 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13910 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13911 boards[0][BOARD_HEIGHT-1-n][1]++;
13913 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13914 n = PieceToNumber(selection);
13915 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13916 boards[0][n][BOARD_WIDTH-1] = selection;
13917 boards[0][n][BOARD_WIDTH-2]++;
13920 boards[0][y][x] = selection;
13921 DrawPosition(TRUE, boards[0]);
13923 fromX = fromY = -1;
13931 DropMenuEvent (ChessSquare selection, int x, int y)
13933 ChessMove moveType;
13935 switch (gameMode) {
13936 case IcsPlayingWhite:
13937 case MachinePlaysBlack:
13938 if (!WhiteOnMove(currentMove)) {
13939 DisplayMoveError(_("It is Black's turn"));
13942 moveType = WhiteDrop;
13944 case IcsPlayingBlack:
13945 case MachinePlaysWhite:
13946 if (WhiteOnMove(currentMove)) {
13947 DisplayMoveError(_("It is White's turn"));
13950 moveType = BlackDrop;
13953 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13959 if (moveType == BlackDrop && selection < BlackPawn) {
13960 selection = (ChessSquare) ((int) selection
13961 + (int) BlackPawn - (int) WhitePawn);
13963 if (boards[currentMove][y][x] != EmptySquare) {
13964 DisplayMoveError(_("That square is occupied"));
13968 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13974 /* Accept a pending offer of any kind from opponent */
13976 if (appData.icsActive) {
13977 SendToICS(ics_prefix);
13978 SendToICS("accept\n");
13979 } else if (cmailMsgLoaded) {
13980 if (currentMove == cmailOldMove &&
13981 commentList[cmailOldMove] != NULL &&
13982 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13983 "Black offers a draw" : "White offers a draw")) {
13985 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13986 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13988 DisplayError(_("There is no pending offer on this move"), 0);
13989 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13992 /* Not used for offers from chess program */
13999 /* Decline a pending offer of any kind from opponent */
14001 if (appData.icsActive) {
14002 SendToICS(ics_prefix);
14003 SendToICS("decline\n");
14004 } else if (cmailMsgLoaded) {
14005 if (currentMove == cmailOldMove &&
14006 commentList[cmailOldMove] != NULL &&
14007 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14008 "Black offers a draw" : "White offers a draw")) {
14010 AppendComment(cmailOldMove, "Draw declined", TRUE);
14011 DisplayComment(cmailOldMove - 1, "Draw declined");
14014 DisplayError(_("There is no pending offer on this move"), 0);
14017 /* Not used for offers from chess program */
14024 /* Issue ICS rematch command */
14025 if (appData.icsActive) {
14026 SendToICS(ics_prefix);
14027 SendToICS("rematch\n");
14034 /* Call your opponent's flag (claim a win on time) */
14035 if (appData.icsActive) {
14036 SendToICS(ics_prefix);
14037 SendToICS("flag\n");
14039 switch (gameMode) {
14042 case MachinePlaysWhite:
14045 GameEnds(GameIsDrawn, "Both players ran out of time",
14048 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14050 DisplayError(_("Your opponent is not out of time"), 0);
14053 case MachinePlaysBlack:
14056 GameEnds(GameIsDrawn, "Both players ran out of time",
14059 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14061 DisplayError(_("Your opponent is not out of time"), 0);
14069 ClockClick (int which)
14070 { // [HGM] code moved to back-end from winboard.c
14071 if(which) { // black clock
14072 if (gameMode == EditPosition || gameMode == IcsExamining) {
14073 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14074 SetBlackToPlayEvent();
14075 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14076 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14077 } else if (shiftKey) {
14078 AdjustClock(which, -1);
14079 } else if (gameMode == IcsPlayingWhite ||
14080 gameMode == MachinePlaysBlack) {
14083 } else { // white clock
14084 if (gameMode == EditPosition || gameMode == IcsExamining) {
14085 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14086 SetWhiteToPlayEvent();
14087 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14088 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14089 } else if (shiftKey) {
14090 AdjustClock(which, -1);
14091 } else if (gameMode == IcsPlayingBlack ||
14092 gameMode == MachinePlaysWhite) {
14101 /* Offer draw or accept pending draw offer from opponent */
14103 if (appData.icsActive) {
14104 /* Note: tournament rules require draw offers to be
14105 made after you make your move but before you punch
14106 your clock. Currently ICS doesn't let you do that;
14107 instead, you immediately punch your clock after making
14108 a move, but you can offer a draw at any time. */
14110 SendToICS(ics_prefix);
14111 SendToICS("draw\n");
14112 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14113 } else if (cmailMsgLoaded) {
14114 if (currentMove == cmailOldMove &&
14115 commentList[cmailOldMove] != NULL &&
14116 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14117 "Black offers a draw" : "White offers a draw")) {
14118 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14119 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14120 } else if (currentMove == cmailOldMove + 1) {
14121 char *offer = WhiteOnMove(cmailOldMove) ?
14122 "White offers a draw" : "Black offers a draw";
14123 AppendComment(currentMove, offer, TRUE);
14124 DisplayComment(currentMove - 1, offer);
14125 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14127 DisplayError(_("You must make your move before offering a draw"), 0);
14128 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14130 } else if (first.offeredDraw) {
14131 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14133 if (first.sendDrawOffers) {
14134 SendToProgram("draw\n", &first);
14135 userOfferedDraw = TRUE;
14143 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14145 if (appData.icsActive) {
14146 SendToICS(ics_prefix);
14147 SendToICS("adjourn\n");
14149 /* Currently GNU Chess doesn't offer or accept Adjourns */
14157 /* Offer Abort or accept pending Abort offer from opponent */
14159 if (appData.icsActive) {
14160 SendToICS(ics_prefix);
14161 SendToICS("abort\n");
14163 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14170 /* Resign. You can do this even if it's not your turn. */
14172 if (appData.icsActive) {
14173 SendToICS(ics_prefix);
14174 SendToICS("resign\n");
14176 switch (gameMode) {
14177 case MachinePlaysWhite:
14178 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14180 case MachinePlaysBlack:
14181 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14184 if (cmailMsgLoaded) {
14186 if (WhiteOnMove(cmailOldMove)) {
14187 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14189 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14191 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14202 StopObservingEvent ()
14204 /* Stop observing current games */
14205 SendToICS(ics_prefix);
14206 SendToICS("unobserve\n");
14210 StopExaminingEvent ()
14212 /* Stop observing current game */
14213 SendToICS(ics_prefix);
14214 SendToICS("unexamine\n");
14218 ForwardInner (int target)
14220 int limit; int oldSeekGraphUp = seekGraphUp;
14222 if (appData.debugMode)
14223 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14224 target, currentMove, forwardMostMove);
14226 if (gameMode == EditPosition)
14229 seekGraphUp = FALSE;
14230 MarkTargetSquares(1);
14232 if (gameMode == PlayFromGameFile && !pausing)
14235 if (gameMode == IcsExamining && pausing)
14236 limit = pauseExamForwardMostMove;
14238 limit = forwardMostMove;
14240 if (target > limit) target = limit;
14242 if (target > 0 && moveList[target - 1][0]) {
14243 int fromX, fromY, toX, toY;
14244 toX = moveList[target - 1][2] - AAA;
14245 toY = moveList[target - 1][3] - ONE;
14246 if (moveList[target - 1][1] == '@') {
14247 if (appData.highlightLastMove) {
14248 SetHighlights(-1, -1, toX, toY);
14251 fromX = moveList[target - 1][0] - AAA;
14252 fromY = moveList[target - 1][1] - ONE;
14253 if (target == currentMove + 1) {
14254 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14256 if (appData.highlightLastMove) {
14257 SetHighlights(fromX, fromY, toX, toY);
14261 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14262 gameMode == Training || gameMode == PlayFromGameFile ||
14263 gameMode == AnalyzeFile) {
14264 while (currentMove < target) {
14265 SendMoveToProgram(currentMove++, &first);
14268 currentMove = target;
14271 if (gameMode == EditGame || gameMode == EndOfGame) {
14272 whiteTimeRemaining = timeRemaining[0][currentMove];
14273 blackTimeRemaining = timeRemaining[1][currentMove];
14275 DisplayBothClocks();
14276 DisplayMove(currentMove - 1);
14277 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14278 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14279 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14280 DisplayComment(currentMove - 1, commentList[currentMove]);
14288 if (gameMode == IcsExamining && !pausing) {
14289 SendToICS(ics_prefix);
14290 SendToICS("forward\n");
14292 ForwardInner(currentMove + 1);
14299 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14300 /* to optimze, we temporarily turn off analysis mode while we feed
14301 * the remaining moves to the engine. Otherwise we get analysis output
14304 if (first.analysisSupport) {
14305 SendToProgram("exit\nforce\n", &first);
14306 first.analyzing = FALSE;
14310 if (gameMode == IcsExamining && !pausing) {
14311 SendToICS(ics_prefix);
14312 SendToICS("forward 999999\n");
14314 ForwardInner(forwardMostMove);
14317 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14318 /* we have fed all the moves, so reactivate analysis mode */
14319 SendToProgram("analyze\n", &first);
14320 first.analyzing = TRUE;
14321 /*first.maybeThinking = TRUE;*/
14322 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14327 BackwardInner (int target)
14329 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14331 if (appData.debugMode)
14332 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14333 target, currentMove, forwardMostMove);
14335 if (gameMode == EditPosition) return;
14336 seekGraphUp = FALSE;
14337 MarkTargetSquares(1);
14338 if (currentMove <= backwardMostMove) {
14340 DrawPosition(full_redraw, boards[currentMove]);
14343 if (gameMode == PlayFromGameFile && !pausing)
14346 if (moveList[target][0]) {
14347 int fromX, fromY, toX, toY;
14348 toX = moveList[target][2] - AAA;
14349 toY = moveList[target][3] - ONE;
14350 if (moveList[target][1] == '@') {
14351 if (appData.highlightLastMove) {
14352 SetHighlights(-1, -1, toX, toY);
14355 fromX = moveList[target][0] - AAA;
14356 fromY = moveList[target][1] - ONE;
14357 if (target == currentMove - 1) {
14358 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14360 if (appData.highlightLastMove) {
14361 SetHighlights(fromX, fromY, toX, toY);
14365 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14366 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14367 while (currentMove > target) {
14368 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14369 // null move cannot be undone. Reload program with move history before it.
14371 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14372 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14374 SendBoard(&first, i);
14375 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14378 SendToProgram("undo\n", &first);
14382 currentMove = target;
14385 if (gameMode == EditGame || gameMode == EndOfGame) {
14386 whiteTimeRemaining = timeRemaining[0][currentMove];
14387 blackTimeRemaining = timeRemaining[1][currentMove];
14389 DisplayBothClocks();
14390 DisplayMove(currentMove - 1);
14391 DrawPosition(full_redraw, boards[currentMove]);
14392 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14393 // [HGM] PV info: routine tests if comment empty
14394 DisplayComment(currentMove - 1, commentList[currentMove]);
14400 if (gameMode == IcsExamining && !pausing) {
14401 SendToICS(ics_prefix);
14402 SendToICS("backward\n");
14404 BackwardInner(currentMove - 1);
14411 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14412 /* to optimize, we temporarily turn off analysis mode while we undo
14413 * all the moves. Otherwise we get analysis output after each undo.
14415 if (first.analysisSupport) {
14416 SendToProgram("exit\nforce\n", &first);
14417 first.analyzing = FALSE;
14421 if (gameMode == IcsExamining && !pausing) {
14422 SendToICS(ics_prefix);
14423 SendToICS("backward 999999\n");
14425 BackwardInner(backwardMostMove);
14428 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14429 /* we have fed all the moves, so reactivate analysis mode */
14430 SendToProgram("analyze\n", &first);
14431 first.analyzing = TRUE;
14432 /*first.maybeThinking = TRUE;*/
14433 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14440 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14441 if (to >= forwardMostMove) to = forwardMostMove;
14442 if (to <= backwardMostMove) to = backwardMostMove;
14443 if (to < currentMove) {
14451 RevertEvent (Boolean annotate)
14453 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14456 if (gameMode != IcsExamining) {
14457 DisplayError(_("You are not examining a game"), 0);
14461 DisplayError(_("You can't revert while pausing"), 0);
14464 SendToICS(ics_prefix);
14465 SendToICS("revert\n");
14469 RetractMoveEvent ()
14471 switch (gameMode) {
14472 case MachinePlaysWhite:
14473 case MachinePlaysBlack:
14474 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14475 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14478 if (forwardMostMove < 2) return;
14479 currentMove = forwardMostMove = forwardMostMove - 2;
14480 whiteTimeRemaining = timeRemaining[0][currentMove];
14481 blackTimeRemaining = timeRemaining[1][currentMove];
14482 DisplayBothClocks();
14483 DisplayMove(currentMove - 1);
14484 ClearHighlights();/*!! could figure this out*/
14485 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14486 SendToProgram("remove\n", &first);
14487 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14490 case BeginningOfGame:
14494 case IcsPlayingWhite:
14495 case IcsPlayingBlack:
14496 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14497 SendToICS(ics_prefix);
14498 SendToICS("takeback 2\n");
14500 SendToICS(ics_prefix);
14501 SendToICS("takeback 1\n");
14510 ChessProgramState *cps;
14512 switch (gameMode) {
14513 case MachinePlaysWhite:
14514 if (!WhiteOnMove(forwardMostMove)) {
14515 DisplayError(_("It is your turn"), 0);
14520 case MachinePlaysBlack:
14521 if (WhiteOnMove(forwardMostMove)) {
14522 DisplayError(_("It is your turn"), 0);
14527 case TwoMachinesPlay:
14528 if (WhiteOnMove(forwardMostMove) ==
14529 (first.twoMachinesColor[0] == 'w')) {
14535 case BeginningOfGame:
14539 SendToProgram("?\n", cps);
14543 TruncateGameEvent ()
14546 if (gameMode != EditGame) return;
14553 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14554 if (forwardMostMove > currentMove) {
14555 if (gameInfo.resultDetails != NULL) {
14556 free(gameInfo.resultDetails);
14557 gameInfo.resultDetails = NULL;
14558 gameInfo.result = GameUnfinished;
14560 forwardMostMove = currentMove;
14561 HistorySet(parseList, backwardMostMove, forwardMostMove,
14569 if (appData.noChessProgram) return;
14570 switch (gameMode) {
14571 case MachinePlaysWhite:
14572 if (WhiteOnMove(forwardMostMove)) {
14573 DisplayError(_("Wait until your turn"), 0);
14577 case BeginningOfGame:
14578 case MachinePlaysBlack:
14579 if (!WhiteOnMove(forwardMostMove)) {
14580 DisplayError(_("Wait until your turn"), 0);
14585 DisplayError(_("No hint available"), 0);
14588 SendToProgram("hint\n", &first);
14589 hintRequested = TRUE;
14595 if (appData.noChessProgram) return;
14596 switch (gameMode) {
14597 case MachinePlaysWhite:
14598 if (WhiteOnMove(forwardMostMove)) {
14599 DisplayError(_("Wait until your turn"), 0);
14603 case BeginningOfGame:
14604 case MachinePlaysBlack:
14605 if (!WhiteOnMove(forwardMostMove)) {
14606 DisplayError(_("Wait until your turn"), 0);
14611 EditPositionDone(TRUE);
14613 case TwoMachinesPlay:
14618 SendToProgram("bk\n", &first);
14619 bookOutput[0] = NULLCHAR;
14620 bookRequested = TRUE;
14626 char *tags = PGNTags(&gameInfo);
14627 TagsPopUp(tags, CmailMsg());
14631 /* end button procedures */
14634 PrintPosition (FILE *fp, int move)
14638 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14639 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14640 char c = PieceToChar(boards[move][i][j]);
14641 fputc(c == 'x' ? '.' : c, fp);
14642 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14645 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14646 fprintf(fp, "white to play\n");
14648 fprintf(fp, "black to play\n");
14652 PrintOpponents (FILE *fp)
14654 if (gameInfo.white != NULL) {
14655 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14661 /* Find last component of program's own name, using some heuristics */
14663 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14666 int local = (strcmp(host, "localhost") == 0);
14667 while (!local && (p = strchr(prog, ';')) != NULL) {
14669 while (*p == ' ') p++;
14672 if (*prog == '"' || *prog == '\'') {
14673 q = strchr(prog + 1, *prog);
14675 q = strchr(prog, ' ');
14677 if (q == NULL) q = prog + strlen(prog);
14679 while (p >= prog && *p != '/' && *p != '\\') p--;
14681 if(p == prog && *p == '"') p++;
14683 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14684 memcpy(buf, p, q - p);
14685 buf[q - p] = NULLCHAR;
14693 TimeControlTagValue ()
14696 if (!appData.clockMode) {
14697 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14698 } else if (movesPerSession > 0) {
14699 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14700 } else if (timeIncrement == 0) {
14701 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14703 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14705 return StrSave(buf);
14711 /* This routine is used only for certain modes */
14712 VariantClass v = gameInfo.variant;
14713 ChessMove r = GameUnfinished;
14716 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14717 r = gameInfo.result;
14718 p = gameInfo.resultDetails;
14719 gameInfo.resultDetails = NULL;
14721 ClearGameInfo(&gameInfo);
14722 gameInfo.variant = v;
14724 switch (gameMode) {
14725 case MachinePlaysWhite:
14726 gameInfo.event = StrSave( appData.pgnEventHeader );
14727 gameInfo.site = StrSave(HostName());
14728 gameInfo.date = PGNDate();
14729 gameInfo.round = StrSave("-");
14730 gameInfo.white = StrSave(first.tidy);
14731 gameInfo.black = StrSave(UserName());
14732 gameInfo.timeControl = TimeControlTagValue();
14735 case MachinePlaysBlack:
14736 gameInfo.event = StrSave( appData.pgnEventHeader );
14737 gameInfo.site = StrSave(HostName());
14738 gameInfo.date = PGNDate();
14739 gameInfo.round = StrSave("-");
14740 gameInfo.white = StrSave(UserName());
14741 gameInfo.black = StrSave(first.tidy);
14742 gameInfo.timeControl = TimeControlTagValue();
14745 case TwoMachinesPlay:
14746 gameInfo.event = StrSave( appData.pgnEventHeader );
14747 gameInfo.site = StrSave(HostName());
14748 gameInfo.date = PGNDate();
14751 snprintf(buf, MSG_SIZ, "%d", roundNr);
14752 gameInfo.round = StrSave(buf);
14754 gameInfo.round = StrSave("-");
14756 if (first.twoMachinesColor[0] == 'w') {
14757 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14758 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14760 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14761 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14763 gameInfo.timeControl = TimeControlTagValue();
14767 gameInfo.event = StrSave("Edited game");
14768 gameInfo.site = StrSave(HostName());
14769 gameInfo.date = PGNDate();
14770 gameInfo.round = StrSave("-");
14771 gameInfo.white = StrSave("-");
14772 gameInfo.black = StrSave("-");
14773 gameInfo.result = r;
14774 gameInfo.resultDetails = p;
14778 gameInfo.event = StrSave("Edited position");
14779 gameInfo.site = StrSave(HostName());
14780 gameInfo.date = PGNDate();
14781 gameInfo.round = StrSave("-");
14782 gameInfo.white = StrSave("-");
14783 gameInfo.black = StrSave("-");
14786 case IcsPlayingWhite:
14787 case IcsPlayingBlack:
14792 case PlayFromGameFile:
14793 gameInfo.event = StrSave("Game from non-PGN file");
14794 gameInfo.site = StrSave(HostName());
14795 gameInfo.date = PGNDate();
14796 gameInfo.round = StrSave("-");
14797 gameInfo.white = StrSave("?");
14798 gameInfo.black = StrSave("?");
14807 ReplaceComment (int index, char *text)
14813 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14814 pvInfoList[index-1].depth == len &&
14815 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14816 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14817 while (*text == '\n') text++;
14818 len = strlen(text);
14819 while (len > 0 && text[len - 1] == '\n') len--;
14821 if (commentList[index] != NULL)
14822 free(commentList[index]);
14825 commentList[index] = NULL;
14828 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14829 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14830 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14831 commentList[index] = (char *) malloc(len + 2);
14832 strncpy(commentList[index], text, len);
14833 commentList[index][len] = '\n';
14834 commentList[index][len + 1] = NULLCHAR;
14836 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14838 commentList[index] = (char *) malloc(len + 7);
14839 safeStrCpy(commentList[index], "{\n", 3);
14840 safeStrCpy(commentList[index]+2, text, len+1);
14841 commentList[index][len+2] = NULLCHAR;
14842 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14843 strcat(commentList[index], "\n}\n");
14848 CrushCRs (char *text)
14856 if (ch == '\r') continue;
14858 } while (ch != '\0');
14862 AppendComment (int index, char *text, Boolean addBraces)
14863 /* addBraces tells if we should add {} */
14868 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14869 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14872 while (*text == '\n') text++;
14873 len = strlen(text);
14874 while (len > 0 && text[len - 1] == '\n') len--;
14875 text[len] = NULLCHAR;
14877 if (len == 0) return;
14879 if (commentList[index] != NULL) {
14880 Boolean addClosingBrace = addBraces;
14881 old = commentList[index];
14882 oldlen = strlen(old);
14883 while(commentList[index][oldlen-1] == '\n')
14884 commentList[index][--oldlen] = NULLCHAR;
14885 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14886 safeStrCpy(commentList[index], old, oldlen + len + 6);
14888 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14889 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14890 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14891 while (*text == '\n') { text++; len--; }
14892 commentList[index][--oldlen] = NULLCHAR;
14894 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14895 else strcat(commentList[index], "\n");
14896 strcat(commentList[index], text);
14897 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14898 else strcat(commentList[index], "\n");
14900 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14902 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14903 else commentList[index][0] = NULLCHAR;
14904 strcat(commentList[index], text);
14905 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14906 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14911 FindStr (char * text, char * sub_text)
14913 char * result = strstr( text, sub_text );
14915 if( result != NULL ) {
14916 result += strlen( sub_text );
14922 /* [AS] Try to extract PV info from PGN comment */
14923 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14925 GetInfoFromComment (int index, char * text)
14927 char * sep = text, *p;
14929 if( text != NULL && index > 0 ) {
14932 int time = -1, sec = 0, deci;
14933 char * s_eval = FindStr( text, "[%eval " );
14934 char * s_emt = FindStr( text, "[%emt " );
14936 if( s_eval != NULL || s_emt != NULL ) {
14940 if( s_eval != NULL ) {
14941 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14945 if( delim != ']' ) {
14950 if( s_emt != NULL ) {
14955 /* We expect something like: [+|-]nnn.nn/dd */
14958 if(*text != '{') return text; // [HGM] braces: must be normal comment
14960 sep = strchr( text, '/' );
14961 if( sep == NULL || sep < (text+4) ) {
14966 if(p[1] == '(') { // comment starts with PV
14967 p = strchr(p, ')'); // locate end of PV
14968 if(p == NULL || sep < p+5) return text;
14969 // at this point we have something like "{(.*) +0.23/6 ..."
14970 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14971 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14972 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14974 time = -1; sec = -1; deci = -1;
14975 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14976 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14977 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14978 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14982 if( score_lo < 0 || score_lo >= 100 ) {
14986 if(sec >= 0) time = 600*time + 10*sec; else
14987 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14989 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14991 /* [HGM] PV time: now locate end of PV info */
14992 while( *++sep >= '0' && *sep <= '9'); // strip depth
14994 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14996 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14998 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14999 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15010 pvInfoList[index-1].depth = depth;
15011 pvInfoList[index-1].score = score;
15012 pvInfoList[index-1].time = 10*time; // centi-sec
15013 if(*sep == '}') *sep = 0; else *--sep = '{';
15014 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15020 SendToProgram (char *message, ChessProgramState *cps)
15022 int count, outCount, error;
15025 if (cps->pr == NoProc) return;
15028 if (appData.debugMode) {
15031 fprintf(debugFP, "%ld >%-6s: %s",
15032 SubtractTimeMarks(&now, &programStartTime),
15033 cps->which, message);
15035 fprintf(serverFP, "%ld >%-6s: %s",
15036 SubtractTimeMarks(&now, &programStartTime),
15037 cps->which, message), fflush(serverFP);
15040 count = strlen(message);
15041 outCount = OutputToProcess(cps->pr, message, count, &error);
15042 if (outCount < count && !exiting
15043 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15044 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15045 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15046 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15047 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15048 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15049 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15050 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15052 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15053 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15054 gameInfo.result = res;
15056 gameInfo.resultDetails = StrSave(buf);
15058 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15059 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15064 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15068 ChessProgramState *cps = (ChessProgramState *)closure;
15070 if (isr != cps->isr) return; /* Killed intentionally */
15073 RemoveInputSource(cps->isr);
15074 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15075 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15076 _(cps->which), cps->program);
15077 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15078 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15079 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15080 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15081 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15083 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15084 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15085 gameInfo.result = res;
15087 gameInfo.resultDetails = StrSave(buf);
15089 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15090 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15092 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15093 _(cps->which), cps->program);
15094 RemoveInputSource(cps->isr);
15096 /* [AS] Program is misbehaving badly... kill it */
15097 if( count == -2 ) {
15098 DestroyChildProcess( cps->pr, 9 );
15102 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15107 if ((end_str = strchr(message, '\r')) != NULL)
15108 *end_str = NULLCHAR;
15109 if ((end_str = strchr(message, '\n')) != NULL)
15110 *end_str = NULLCHAR;
15112 if (appData.debugMode) {
15113 TimeMark now; int print = 1;
15114 char *quote = ""; char c; int i;
15116 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15117 char start = message[0];
15118 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15119 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15120 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15121 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15122 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15123 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15124 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15125 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15126 sscanf(message, "hint: %c", &c)!=1 &&
15127 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15128 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15129 print = (appData.engineComments >= 2);
15131 message[0] = start; // restore original message
15135 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15136 SubtractTimeMarks(&now, &programStartTime), cps->which,
15140 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15141 SubtractTimeMarks(&now, &programStartTime), cps->which,
15143 message), fflush(serverFP);
15147 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15148 if (appData.icsEngineAnalyze) {
15149 if (strstr(message, "whisper") != NULL ||
15150 strstr(message, "kibitz") != NULL ||
15151 strstr(message, "tellics") != NULL) return;
15154 HandleMachineMove(message, cps);
15159 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15164 if( timeControl_2 > 0 ) {
15165 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15166 tc = timeControl_2;
15169 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15170 inc /= cps->timeOdds;
15171 st /= cps->timeOdds;
15173 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15176 /* Set exact time per move, normally using st command */
15177 if (cps->stKludge) {
15178 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15180 if (seconds == 0) {
15181 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15183 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15186 snprintf(buf, MSG_SIZ, "st %d\n", st);
15189 /* Set conventional or incremental time control, using level command */
15190 if (seconds == 0) {
15191 /* Note old gnuchess bug -- minutes:seconds used to not work.
15192 Fixed in later versions, but still avoid :seconds
15193 when seconds is 0. */
15194 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15196 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15197 seconds, inc/1000.);
15200 SendToProgram(buf, cps);
15202 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15203 /* Orthogonally, limit search to given depth */
15205 if (cps->sdKludge) {
15206 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15208 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15210 SendToProgram(buf, cps);
15213 if(cps->nps >= 0) { /* [HGM] nps */
15214 if(cps->supportsNPS == FALSE)
15215 cps->nps = -1; // don't use if engine explicitly says not supported!
15217 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15218 SendToProgram(buf, cps);
15223 ChessProgramState *
15225 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15227 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15228 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15234 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15236 char message[MSG_SIZ];
15239 /* Note: this routine must be called when the clocks are stopped
15240 or when they have *just* been set or switched; otherwise
15241 it will be off by the time since the current tick started.
15243 if (machineWhite) {
15244 time = whiteTimeRemaining / 10;
15245 otime = blackTimeRemaining / 10;
15247 time = blackTimeRemaining / 10;
15248 otime = whiteTimeRemaining / 10;
15250 /* [HGM] translate opponent's time by time-odds factor */
15251 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15253 if (time <= 0) time = 1;
15254 if (otime <= 0) otime = 1;
15256 snprintf(message, MSG_SIZ, "time %ld\n", time);
15257 SendToProgram(message, cps);
15259 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15260 SendToProgram(message, cps);
15264 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15267 int len = strlen(name);
15270 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15272 sscanf(*p, "%d", &val);
15274 while (**p && **p != ' ')
15276 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15277 SendToProgram(buf, cps);
15284 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15287 int len = strlen(name);
15288 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15290 sscanf(*p, "%d", loc);
15291 while (**p && **p != ' ') (*p)++;
15292 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15293 SendToProgram(buf, cps);
15300 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15303 int len = strlen(name);
15304 if (strncmp((*p), name, len) == 0
15305 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15307 sscanf(*p, "%[^\"]", loc);
15308 while (**p && **p != '\"') (*p)++;
15309 if (**p == '\"') (*p)++;
15310 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15311 SendToProgram(buf, cps);
15318 ParseOption (Option *opt, ChessProgramState *cps)
15319 // [HGM] options: process the string that defines an engine option, and determine
15320 // name, type, default value, and allowed value range
15322 char *p, *q, buf[MSG_SIZ];
15323 int n, min = (-1)<<31, max = 1<<31, def;
15325 if(p = strstr(opt->name, " -spin ")) {
15326 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15327 if(max < min) max = min; // enforce consistency
15328 if(def < min) def = min;
15329 if(def > max) def = max;
15334 } else if((p = strstr(opt->name, " -slider "))) {
15335 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15336 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15337 if(max < min) max = min; // enforce consistency
15338 if(def < min) def = min;
15339 if(def > max) def = max;
15343 opt->type = Spin; // Slider;
15344 } else if((p = strstr(opt->name, " -string "))) {
15345 opt->textValue = p+9;
15346 opt->type = TextBox;
15347 } else if((p = strstr(opt->name, " -file "))) {
15348 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15349 opt->textValue = p+7;
15350 opt->type = FileName; // FileName;
15351 } else if((p = strstr(opt->name, " -path "))) {
15352 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15353 opt->textValue = p+7;
15354 opt->type = PathName; // PathName;
15355 } else if(p = strstr(opt->name, " -check ")) {
15356 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15357 opt->value = (def != 0);
15358 opt->type = CheckBox;
15359 } else if(p = strstr(opt->name, " -combo ")) {
15360 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15361 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15362 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15363 opt->value = n = 0;
15364 while(q = StrStr(q, " /// ")) {
15365 n++; *q = 0; // count choices, and null-terminate each of them
15367 if(*q == '*') { // remember default, which is marked with * prefix
15371 cps->comboList[cps->comboCnt++] = q;
15373 cps->comboList[cps->comboCnt++] = NULL;
15375 opt->type = ComboBox;
15376 } else if(p = strstr(opt->name, " -button")) {
15377 opt->type = Button;
15378 } else if(p = strstr(opt->name, " -save")) {
15379 opt->type = SaveButton;
15380 } else return FALSE;
15381 *p = 0; // terminate option name
15382 // now look if the command-line options define a setting for this engine option.
15383 if(cps->optionSettings && cps->optionSettings[0])
15384 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15385 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15386 snprintf(buf, MSG_SIZ, "option %s", p);
15387 if(p = strstr(buf, ",")) *p = 0;
15388 if(q = strchr(buf, '=')) switch(opt->type) {
15390 for(n=0; n<opt->max; n++)
15391 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15394 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15398 opt->value = atoi(q+1);
15403 SendToProgram(buf, cps);
15409 FeatureDone (ChessProgramState *cps, int val)
15411 DelayedEventCallback cb = GetDelayedEvent();
15412 if ((cb == InitBackEnd3 && cps == &first) ||
15413 (cb == SettingsMenuIfReady && cps == &second) ||
15414 (cb == LoadEngine) ||
15415 (cb == TwoMachinesEventIfReady)) {
15416 CancelDelayedEvent();
15417 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15419 cps->initDone = val;
15422 /* Parse feature command from engine */
15424 ParseFeatures (char *args, ChessProgramState *cps)
15432 while (*p == ' ') p++;
15433 if (*p == NULLCHAR) return;
15435 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15436 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15437 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15438 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15439 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15440 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15441 if (BoolFeature(&p, "reuse", &val, cps)) {
15442 /* Engine can disable reuse, but can't enable it if user said no */
15443 if (!val) cps->reuse = FALSE;
15446 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15447 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15448 if (gameMode == TwoMachinesPlay) {
15449 DisplayTwoMachinesTitle();
15455 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15456 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15457 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15458 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15459 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15460 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15461 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15462 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15463 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15464 if (IntFeature(&p, "done", &val, cps)) {
15465 FeatureDone(cps, val);
15468 /* Added by Tord: */
15469 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15470 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15471 /* End of additions by Tord */
15473 /* [HGM] added features: */
15474 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15475 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15476 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15477 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15478 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15479 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15480 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15481 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15482 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15483 SendToProgram(buf, cps);
15486 if(cps->nrOptions >= MAX_OPTIONS) {
15488 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15489 DisplayError(buf, 0);
15493 /* End of additions by HGM */
15495 /* unknown feature: complain and skip */
15497 while (*q && *q != '=') q++;
15498 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15499 SendToProgram(buf, cps);
15505 while (*p && *p != '\"') p++;
15506 if (*p == '\"') p++;
15508 while (*p && *p != ' ') p++;
15516 PeriodicUpdatesEvent (int newState)
15518 if (newState == appData.periodicUpdates)
15521 appData.periodicUpdates=newState;
15523 /* Display type changes, so update it now */
15524 // DisplayAnalysis();
15526 /* Get the ball rolling again... */
15528 AnalysisPeriodicEvent(1);
15529 StartAnalysisClock();
15534 PonderNextMoveEvent (int newState)
15536 if (newState == appData.ponderNextMove) return;
15537 if (gameMode == EditPosition) EditPositionDone(TRUE);
15539 SendToProgram("hard\n", &first);
15540 if (gameMode == TwoMachinesPlay) {
15541 SendToProgram("hard\n", &second);
15544 SendToProgram("easy\n", &first);
15545 thinkOutput[0] = NULLCHAR;
15546 if (gameMode == TwoMachinesPlay) {
15547 SendToProgram("easy\n", &second);
15550 appData.ponderNextMove = newState;
15554 NewSettingEvent (int option, int *feature, char *command, int value)
15558 if (gameMode == EditPosition) EditPositionDone(TRUE);
15559 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15560 if(feature == NULL || *feature) SendToProgram(buf, &first);
15561 if (gameMode == TwoMachinesPlay) {
15562 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15567 ShowThinkingEvent ()
15568 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15570 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15571 int newState = appData.showThinking
15572 // [HGM] thinking: other features now need thinking output as well
15573 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15575 if (oldState == newState) return;
15576 oldState = newState;
15577 if (gameMode == EditPosition) EditPositionDone(TRUE);
15579 SendToProgram("post\n", &first);
15580 if (gameMode == TwoMachinesPlay) {
15581 SendToProgram("post\n", &second);
15584 SendToProgram("nopost\n", &first);
15585 thinkOutput[0] = NULLCHAR;
15586 if (gameMode == TwoMachinesPlay) {
15587 SendToProgram("nopost\n", &second);
15590 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15594 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15596 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15597 if (pr == NoProc) return;
15598 AskQuestion(title, question, replyPrefix, pr);
15602 TypeInEvent (char firstChar)
15604 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15605 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15606 gameMode == AnalyzeMode || gameMode == EditGame ||
15607 gameMode == EditPosition || gameMode == IcsExamining ||
15608 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15609 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15610 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15611 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15612 gameMode == Training) PopUpMoveDialog(firstChar);
15616 TypeInDoneEvent (char *move)
15619 int n, fromX, fromY, toX, toY;
15621 ChessMove moveType;
15624 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15625 EditPositionPasteFEN(move);
15628 // [HGM] movenum: allow move number to be typed in any mode
15629 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15633 // undocumented kludge: allow command-line option to be typed in!
15634 // (potentially fatal, and does not implement the effect of the option.)
15635 // should only be used for options that are values on which future decisions will be made,
15636 // and definitely not on options that would be used during initialization.
15637 if(strstr(move, "!!! -") == move) {
15638 ParseArgsFromString(move+4);
15642 if (gameMode != EditGame && currentMove != forwardMostMove &&
15643 gameMode != Training) {
15644 DisplayMoveError(_("Displayed move is not current"));
15646 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15647 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15648 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15649 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15650 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15651 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15653 DisplayMoveError(_("Could not parse move"));
15659 DisplayMove (int moveNumber)
15661 char message[MSG_SIZ];
15663 char cpThinkOutput[MSG_SIZ];
15665 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15667 if (moveNumber == forwardMostMove - 1 ||
15668 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15670 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15672 if (strchr(cpThinkOutput, '\n')) {
15673 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15676 *cpThinkOutput = NULLCHAR;
15679 /* [AS] Hide thinking from human user */
15680 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15681 *cpThinkOutput = NULLCHAR;
15682 if( thinkOutput[0] != NULLCHAR ) {
15685 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15686 cpThinkOutput[i] = '.';
15688 cpThinkOutput[i] = NULLCHAR;
15689 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15693 if (moveNumber == forwardMostMove - 1 &&
15694 gameInfo.resultDetails != NULL) {
15695 if (gameInfo.resultDetails[0] == NULLCHAR) {
15696 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15698 snprintf(res, MSG_SIZ, " {%s} %s",
15699 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15705 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15706 DisplayMessage(res, cpThinkOutput);
15708 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15709 WhiteOnMove(moveNumber) ? " " : ".. ",
15710 parseList[moveNumber], res);
15711 DisplayMessage(message, cpThinkOutput);
15716 DisplayComment (int moveNumber, char *text)
15718 char title[MSG_SIZ];
15720 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15721 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15723 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15724 WhiteOnMove(moveNumber) ? " " : ".. ",
15725 parseList[moveNumber]);
15727 if (text != NULL && (appData.autoDisplayComment || commentUp))
15728 CommentPopUp(title, text);
15731 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15732 * might be busy thinking or pondering. It can be omitted if your
15733 * gnuchess is configured to stop thinking immediately on any user
15734 * input. However, that gnuchess feature depends on the FIONREAD
15735 * ioctl, which does not work properly on some flavors of Unix.
15738 Attention (ChessProgramState *cps)
15741 if (!cps->useSigint) return;
15742 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15743 switch (gameMode) {
15744 case MachinePlaysWhite:
15745 case MachinePlaysBlack:
15746 case TwoMachinesPlay:
15747 case IcsPlayingWhite:
15748 case IcsPlayingBlack:
15751 /* Skip if we know it isn't thinking */
15752 if (!cps->maybeThinking) return;
15753 if (appData.debugMode)
15754 fprintf(debugFP, "Interrupting %s\n", cps->which);
15755 InterruptChildProcess(cps->pr);
15756 cps->maybeThinking = FALSE;
15761 #endif /*ATTENTION*/
15767 if (whiteTimeRemaining <= 0) {
15770 if (appData.icsActive) {
15771 if (appData.autoCallFlag &&
15772 gameMode == IcsPlayingBlack && !blackFlag) {
15773 SendToICS(ics_prefix);
15774 SendToICS("flag\n");
15778 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15780 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15781 if (appData.autoCallFlag) {
15782 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15789 if (blackTimeRemaining <= 0) {
15792 if (appData.icsActive) {
15793 if (appData.autoCallFlag &&
15794 gameMode == IcsPlayingWhite && !whiteFlag) {
15795 SendToICS(ics_prefix);
15796 SendToICS("flag\n");
15800 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15802 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15803 if (appData.autoCallFlag) {
15804 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15815 CheckTimeControl ()
15817 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15818 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15821 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15823 if ( !WhiteOnMove(forwardMostMove) ) {
15824 /* White made time control */
15825 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15826 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15827 /* [HGM] time odds: correct new time quota for time odds! */
15828 / WhitePlayer()->timeOdds;
15829 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15831 lastBlack -= blackTimeRemaining;
15832 /* Black made time control */
15833 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15834 / WhitePlayer()->other->timeOdds;
15835 lastWhite = whiteTimeRemaining;
15840 DisplayBothClocks ()
15842 int wom = gameMode == EditPosition ?
15843 !blackPlaysFirst : WhiteOnMove(currentMove);
15844 DisplayWhiteClock(whiteTimeRemaining, wom);
15845 DisplayBlackClock(blackTimeRemaining, !wom);
15849 /* Timekeeping seems to be a portability nightmare. I think everyone
15850 has ftime(), but I'm really not sure, so I'm including some ifdefs
15851 to use other calls if you don't. Clocks will be less accurate if
15852 you have neither ftime nor gettimeofday.
15855 /* VS 2008 requires the #include outside of the function */
15856 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15857 #include <sys/timeb.h>
15860 /* Get the current time as a TimeMark */
15862 GetTimeMark (TimeMark *tm)
15864 #if HAVE_GETTIMEOFDAY
15866 struct timeval timeVal;
15867 struct timezone timeZone;
15869 gettimeofday(&timeVal, &timeZone);
15870 tm->sec = (long) timeVal.tv_sec;
15871 tm->ms = (int) (timeVal.tv_usec / 1000L);
15873 #else /*!HAVE_GETTIMEOFDAY*/
15876 // include <sys/timeb.h> / moved to just above start of function
15877 struct timeb timeB;
15880 tm->sec = (long) timeB.time;
15881 tm->ms = (int) timeB.millitm;
15883 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15884 tm->sec = (long) time(NULL);
15890 /* Return the difference in milliseconds between two
15891 time marks. We assume the difference will fit in a long!
15894 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15896 return 1000L*(tm2->sec - tm1->sec) +
15897 (long) (tm2->ms - tm1->ms);
15902 * Code to manage the game clocks.
15904 * In tournament play, black starts the clock and then white makes a move.
15905 * We give the human user a slight advantage if he is playing white---the
15906 * clocks don't run until he makes his first move, so it takes zero time.
15907 * Also, we don't account for network lag, so we could get out of sync
15908 * with GNU Chess's clock -- but then, referees are always right.
15911 static TimeMark tickStartTM;
15912 static long intendedTickLength;
15915 NextTickLength (long timeRemaining)
15917 long nominalTickLength, nextTickLength;
15919 if (timeRemaining > 0L && timeRemaining <= 10000L)
15920 nominalTickLength = 100L;
15922 nominalTickLength = 1000L;
15923 nextTickLength = timeRemaining % nominalTickLength;
15924 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15926 return nextTickLength;
15929 /* Adjust clock one minute up or down */
15931 AdjustClock (Boolean which, int dir)
15933 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15934 if(which) blackTimeRemaining += 60000*dir;
15935 else whiteTimeRemaining += 60000*dir;
15936 DisplayBothClocks();
15937 adjustedClock = TRUE;
15940 /* Stop clocks and reset to a fresh time control */
15944 (void) StopClockTimer();
15945 if (appData.icsActive) {
15946 whiteTimeRemaining = blackTimeRemaining = 0;
15947 } else if (searchTime) {
15948 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15949 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15950 } else { /* [HGM] correct new time quote for time odds */
15951 whiteTC = blackTC = fullTimeControlString;
15952 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15953 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15955 if (whiteFlag || blackFlag) {
15957 whiteFlag = blackFlag = FALSE;
15959 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15960 DisplayBothClocks();
15961 adjustedClock = FALSE;
15964 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15966 /* Decrement running clock by amount of time that has passed */
15970 long timeRemaining;
15971 long lastTickLength, fudge;
15974 if (!appData.clockMode) return;
15975 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15979 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15981 /* Fudge if we woke up a little too soon */
15982 fudge = intendedTickLength - lastTickLength;
15983 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15985 if (WhiteOnMove(forwardMostMove)) {
15986 if(whiteNPS >= 0) lastTickLength = 0;
15987 timeRemaining = whiteTimeRemaining -= lastTickLength;
15988 if(timeRemaining < 0 && !appData.icsActive) {
15989 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15990 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15991 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15992 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15995 DisplayWhiteClock(whiteTimeRemaining - fudge,
15996 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15998 if(blackNPS >= 0) lastTickLength = 0;
15999 timeRemaining = blackTimeRemaining -= lastTickLength;
16000 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16001 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16003 blackStartMove = forwardMostMove;
16004 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16007 DisplayBlackClock(blackTimeRemaining - fudge,
16008 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16010 if (CheckFlags()) return;
16013 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16014 StartClockTimer(intendedTickLength);
16016 /* if the time remaining has fallen below the alarm threshold, sound the
16017 * alarm. if the alarm has sounded and (due to a takeback or time control
16018 * with increment) the time remaining has increased to a level above the
16019 * threshold, reset the alarm so it can sound again.
16022 if (appData.icsActive && appData.icsAlarm) {
16024 /* make sure we are dealing with the user's clock */
16025 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16026 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16029 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16030 alarmSounded = FALSE;
16031 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16033 alarmSounded = TRUE;
16039 /* A player has just moved, so stop the previously running
16040 clock and (if in clock mode) start the other one.
16041 We redisplay both clocks in case we're in ICS mode, because
16042 ICS gives us an update to both clocks after every move.
16043 Note that this routine is called *after* forwardMostMove
16044 is updated, so the last fractional tick must be subtracted
16045 from the color that is *not* on move now.
16048 SwitchClocks (int newMoveNr)
16050 long lastTickLength;
16052 int flagged = FALSE;
16056 if (StopClockTimer() && appData.clockMode) {
16057 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16058 if (!WhiteOnMove(forwardMostMove)) {
16059 if(blackNPS >= 0) lastTickLength = 0;
16060 blackTimeRemaining -= lastTickLength;
16061 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16062 // if(pvInfoList[forwardMostMove].time == -1)
16063 pvInfoList[forwardMostMove].time = // use GUI time
16064 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16066 if(whiteNPS >= 0) lastTickLength = 0;
16067 whiteTimeRemaining -= lastTickLength;
16068 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16069 // if(pvInfoList[forwardMostMove].time == -1)
16070 pvInfoList[forwardMostMove].time =
16071 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16073 flagged = CheckFlags();
16075 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16076 CheckTimeControl();
16078 if (flagged || !appData.clockMode) return;
16080 switch (gameMode) {
16081 case MachinePlaysBlack:
16082 case MachinePlaysWhite:
16083 case BeginningOfGame:
16084 if (pausing) return;
16088 case PlayFromGameFile:
16096 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16097 if(WhiteOnMove(forwardMostMove))
16098 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16099 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16103 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16104 whiteTimeRemaining : blackTimeRemaining);
16105 StartClockTimer(intendedTickLength);
16109 /* Stop both clocks */
16113 long lastTickLength;
16116 if (!StopClockTimer()) return;
16117 if (!appData.clockMode) return;
16121 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16122 if (WhiteOnMove(forwardMostMove)) {
16123 if(whiteNPS >= 0) lastTickLength = 0;
16124 whiteTimeRemaining -= lastTickLength;
16125 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16127 if(blackNPS >= 0) lastTickLength = 0;
16128 blackTimeRemaining -= lastTickLength;
16129 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16134 /* Start clock of player on move. Time may have been reset, so
16135 if clock is already running, stop and restart it. */
16139 (void) StopClockTimer(); /* in case it was running already */
16140 DisplayBothClocks();
16141 if (CheckFlags()) return;
16143 if (!appData.clockMode) return;
16144 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16146 GetTimeMark(&tickStartTM);
16147 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16148 whiteTimeRemaining : blackTimeRemaining);
16150 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16151 whiteNPS = blackNPS = -1;
16152 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16153 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16154 whiteNPS = first.nps;
16155 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16156 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16157 blackNPS = first.nps;
16158 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16159 whiteNPS = second.nps;
16160 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16161 blackNPS = second.nps;
16162 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16164 StartClockTimer(intendedTickLength);
16168 TimeString (long ms)
16170 long second, minute, hour, day;
16172 static char buf[32];
16174 if (ms > 0 && ms <= 9900) {
16175 /* convert milliseconds to tenths, rounding up */
16176 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16178 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16182 /* convert milliseconds to seconds, rounding up */
16183 /* use floating point to avoid strangeness of integer division
16184 with negative dividends on many machines */
16185 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16192 day = second / (60 * 60 * 24);
16193 second = second % (60 * 60 * 24);
16194 hour = second / (60 * 60);
16195 second = second % (60 * 60);
16196 minute = second / 60;
16197 second = second % 60;
16200 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16201 sign, day, hour, minute, second);
16203 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16205 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16212 * This is necessary because some C libraries aren't ANSI C compliant yet.
16215 StrStr (char *string, char *match)
16219 length = strlen(match);
16221 for (i = strlen(string) - length; i >= 0; i--, string++)
16222 if (!strncmp(match, string, length))
16229 StrCaseStr (char *string, char *match)
16233 length = strlen(match);
16235 for (i = strlen(string) - length; i >= 0; i--, string++) {
16236 for (j = 0; j < length; j++) {
16237 if (ToLower(match[j]) != ToLower(string[j]))
16240 if (j == length) return string;
16248 StrCaseCmp (char *s1, char *s2)
16253 c1 = ToLower(*s1++);
16254 c2 = ToLower(*s2++);
16255 if (c1 > c2) return 1;
16256 if (c1 < c2) return -1;
16257 if (c1 == NULLCHAR) return 0;
16265 return isupper(c) ? tolower(c) : c;
16272 return islower(c) ? toupper(c) : c;
16274 #endif /* !_amigados */
16281 if ((ret = (char *) malloc(strlen(s) + 1)))
16283 safeStrCpy(ret, s, strlen(s)+1);
16289 StrSavePtr (char *s, char **savePtr)
16294 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16295 safeStrCpy(*savePtr, s, strlen(s)+1);
16307 clock = time((time_t *)NULL);
16308 tm = localtime(&clock);
16309 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16310 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16311 return StrSave(buf);
16316 PositionToFEN (int move, char *overrideCastling)
16318 int i, j, fromX, fromY, toX, toY;
16325 whiteToPlay = (gameMode == EditPosition) ?
16326 !blackPlaysFirst : (move % 2 == 0);
16329 /* Piece placement data */
16330 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16331 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16333 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16334 if (boards[move][i][j] == EmptySquare) {
16336 } else { ChessSquare piece = boards[move][i][j];
16337 if (emptycount > 0) {
16338 if(emptycount<10) /* [HGM] can be >= 10 */
16339 *p++ = '0' + emptycount;
16340 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16343 if(PieceToChar(piece) == '+') {
16344 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16346 piece = (ChessSquare)(DEMOTED piece);
16348 *p++ = PieceToChar(piece);
16350 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16351 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16356 if (emptycount > 0) {
16357 if(emptycount<10) /* [HGM] can be >= 10 */
16358 *p++ = '0' + emptycount;
16359 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16366 /* [HGM] print Crazyhouse or Shogi holdings */
16367 if( gameInfo.holdingsWidth ) {
16368 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16370 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16371 piece = boards[move][i][BOARD_WIDTH-1];
16372 if( piece != EmptySquare )
16373 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16374 *p++ = PieceToChar(piece);
16376 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16377 piece = boards[move][BOARD_HEIGHT-i-1][0];
16378 if( piece != EmptySquare )
16379 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16380 *p++ = PieceToChar(piece);
16383 if( q == p ) *p++ = '-';
16389 *p++ = whiteToPlay ? 'w' : 'b';
16392 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16393 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16395 if(nrCastlingRights) {
16397 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16398 /* [HGM] write directly from rights */
16399 if(boards[move][CASTLING][2] != NoRights &&
16400 boards[move][CASTLING][0] != NoRights )
16401 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16402 if(boards[move][CASTLING][2] != NoRights &&
16403 boards[move][CASTLING][1] != NoRights )
16404 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16405 if(boards[move][CASTLING][5] != NoRights &&
16406 boards[move][CASTLING][3] != NoRights )
16407 *p++ = boards[move][CASTLING][3] + AAA;
16408 if(boards[move][CASTLING][5] != NoRights &&
16409 boards[move][CASTLING][4] != NoRights )
16410 *p++ = boards[move][CASTLING][4] + AAA;
16413 /* [HGM] write true castling rights */
16414 if( nrCastlingRights == 6 ) {
16415 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16416 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16417 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16418 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16419 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16420 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16421 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16422 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16425 if (q == p) *p++ = '-'; /* No castling rights */
16429 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16430 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16431 /* En passant target square */
16432 if (move > backwardMostMove) {
16433 fromX = moveList[move - 1][0] - AAA;
16434 fromY = moveList[move - 1][1] - ONE;
16435 toX = moveList[move - 1][2] - AAA;
16436 toY = moveList[move - 1][3] - ONE;
16437 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16438 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16439 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16441 /* 2-square pawn move just happened */
16443 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16447 } else if(move == backwardMostMove) {
16448 // [HGM] perhaps we should always do it like this, and forget the above?
16449 if((signed char)boards[move][EP_STATUS] >= 0) {
16450 *p++ = boards[move][EP_STATUS] + AAA;
16451 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16462 /* [HGM] find reversible plies */
16463 { int i = 0, j=move;
16465 if (appData.debugMode) { int k;
16466 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16467 for(k=backwardMostMove; k<=forwardMostMove; k++)
16468 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16472 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16473 if( j == backwardMostMove ) i += initialRulePlies;
16474 sprintf(p, "%d ", i);
16475 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16477 /* Fullmove number */
16478 sprintf(p, "%d", (move / 2) + 1);
16480 return StrSave(buf);
16484 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16493 /* [HGM] by default clear Crazyhouse holdings, if present */
16494 if(gameInfo.holdingsWidth) {
16495 for(i=0; i<BOARD_HEIGHT; i++) {
16496 board[i][0] = EmptySquare; /* black holdings */
16497 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16498 board[i][1] = (ChessSquare) 0; /* black counts */
16499 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16503 /* Piece placement data */
16504 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16507 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16508 if (*p == '/') p++;
16509 emptycount = gameInfo.boardWidth - j;
16510 while (emptycount--)
16511 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16513 #if(BOARD_FILES >= 10)
16514 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16515 p++; emptycount=10;
16516 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16517 while (emptycount--)
16518 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16520 } else if (isdigit(*p)) {
16521 emptycount = *p++ - '0';
16522 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16523 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16524 while (emptycount--)
16525 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16526 } else if (*p == '+' || isalpha(*p)) {
16527 if (j >= gameInfo.boardWidth) return FALSE;
16529 piece = CharToPiece(*++p);
16530 if(piece == EmptySquare) return FALSE; /* unknown piece */
16531 piece = (ChessSquare) (PROMOTED piece ); p++;
16532 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16533 } else piece = CharToPiece(*p++);
16535 if(piece==EmptySquare) return FALSE; /* unknown piece */
16536 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16537 piece = (ChessSquare) (PROMOTED piece);
16538 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16541 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16547 while (*p == '/' || *p == ' ') p++;
16549 /* [HGM] look for Crazyhouse holdings here */
16550 while(*p==' ') p++;
16551 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16553 if(*p == '-' ) p++; /* empty holdings */ else {
16554 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16555 /* if we would allow FEN reading to set board size, we would */
16556 /* have to add holdings and shift the board read so far here */
16557 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16559 if((int) piece >= (int) BlackPawn ) {
16560 i = (int)piece - (int)BlackPawn;
16561 i = PieceToNumber((ChessSquare)i);
16562 if( i >= gameInfo.holdingsSize ) return FALSE;
16563 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16564 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16566 i = (int)piece - (int)WhitePawn;
16567 i = PieceToNumber((ChessSquare)i);
16568 if( i >= gameInfo.holdingsSize ) return FALSE;
16569 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16570 board[i][BOARD_WIDTH-2]++; /* black holdings */
16577 while(*p == ' ') p++;
16581 if(appData.colorNickNames) {
16582 if( c == appData.colorNickNames[0] ) c = 'w'; else
16583 if( c == appData.colorNickNames[1] ) c = 'b';
16587 *blackPlaysFirst = FALSE;
16590 *blackPlaysFirst = TRUE;
16596 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16597 /* return the extra info in global variiables */
16599 /* set defaults in case FEN is incomplete */
16600 board[EP_STATUS] = EP_UNKNOWN;
16601 for(i=0; i<nrCastlingRights; i++ ) {
16602 board[CASTLING][i] =
16603 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16604 } /* assume possible unless obviously impossible */
16605 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16606 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16607 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16608 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16609 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16610 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16611 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16612 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16615 while(*p==' ') p++;
16616 if(nrCastlingRights) {
16617 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16618 /* castling indicator present, so default becomes no castlings */
16619 for(i=0; i<nrCastlingRights; i++ ) {
16620 board[CASTLING][i] = NoRights;
16623 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16624 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16625 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16626 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16627 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16629 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16630 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16631 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16633 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16634 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16635 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16636 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16637 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16638 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16641 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16642 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16643 board[CASTLING][2] = whiteKingFile;
16646 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16647 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16648 board[CASTLING][2] = whiteKingFile;
16651 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16652 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16653 board[CASTLING][5] = blackKingFile;
16656 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16657 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16658 board[CASTLING][5] = blackKingFile;
16661 default: /* FRC castlings */
16662 if(c >= 'a') { /* black rights */
16663 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16664 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16665 if(i == BOARD_RGHT) break;
16666 board[CASTLING][5] = i;
16668 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16669 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16671 board[CASTLING][3] = c;
16673 board[CASTLING][4] = c;
16674 } else { /* white rights */
16675 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16676 if(board[0][i] == WhiteKing) break;
16677 if(i == BOARD_RGHT) break;
16678 board[CASTLING][2] = i;
16679 c -= AAA - 'a' + 'A';
16680 if(board[0][c] >= WhiteKing) break;
16682 board[CASTLING][0] = c;
16684 board[CASTLING][1] = c;
16688 for(i=0; i<nrCastlingRights; i++)
16689 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16690 if (appData.debugMode) {
16691 fprintf(debugFP, "FEN castling rights:");
16692 for(i=0; i<nrCastlingRights; i++)
16693 fprintf(debugFP, " %d", board[CASTLING][i]);
16694 fprintf(debugFP, "\n");
16697 while(*p==' ') p++;
16700 /* read e.p. field in games that know e.p. capture */
16701 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16702 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16704 p++; board[EP_STATUS] = EP_NONE;
16706 char c = *p++ - AAA;
16708 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16709 if(*p >= '0' && *p <='9') p++;
16710 board[EP_STATUS] = c;
16715 if(sscanf(p, "%d", &i) == 1) {
16716 FENrulePlies = i; /* 50-move ply counter */
16717 /* (The move number is still ignored) */
16724 EditPositionPasteFEN (char *fen)
16727 Board initial_position;
16729 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16730 DisplayError(_("Bad FEN position in clipboard"), 0);
16733 int savedBlackPlaysFirst = blackPlaysFirst;
16734 EditPositionEvent();
16735 blackPlaysFirst = savedBlackPlaysFirst;
16736 CopyBoard(boards[0], initial_position);
16737 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16738 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16739 DisplayBothClocks();
16740 DrawPosition(FALSE, boards[currentMove]);
16745 static char cseq[12] = "\\ ";
16748 set_cont_sequence (char *new_seq)
16753 // handle bad attempts to set the sequence
16755 return 0; // acceptable error - no debug
16757 len = strlen(new_seq);
16758 ret = (len > 0) && (len < sizeof(cseq));
16760 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16761 else if (appData.debugMode)
16762 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16767 reformat a source message so words don't cross the width boundary. internal
16768 newlines are not removed. returns the wrapped size (no null character unless
16769 included in source message). If dest is NULL, only calculate the size required
16770 for the dest buffer. lp argument indicats line position upon entry, and it's
16771 passed back upon exit.
16774 wrap (char *dest, char *src, int count, int width, int *lp)
16776 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16778 cseq_len = strlen(cseq);
16779 old_line = line = *lp;
16780 ansi = len = clen = 0;
16782 for (i=0; i < count; i++)
16784 if (src[i] == '\033')
16787 // if we hit the width, back up
16788 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16790 // store i & len in case the word is too long
16791 old_i = i, old_len = len;
16793 // find the end of the last word
16794 while (i && src[i] != ' ' && src[i] != '\n')
16800 // word too long? restore i & len before splitting it
16801 if ((old_i-i+clen) >= width)
16808 if (i && src[i-1] == ' ')
16811 if (src[i] != ' ' && src[i] != '\n')
16818 // now append the newline and continuation sequence
16823 strncpy(dest+len, cseq, cseq_len);
16831 dest[len] = src[i];
16835 if (src[i] == '\n')
16840 if (dest && appData.debugMode)
16842 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16843 count, width, line, len, *lp);
16844 show_bytes(debugFP, src, count);
16845 fprintf(debugFP, "\ndest: ");
16846 show_bytes(debugFP, dest, len);
16847 fprintf(debugFP, "\n");
16849 *lp = dest ? line : old_line;
16854 // [HGM] vari: routines for shelving variations
16855 Boolean modeRestore = FALSE;
16858 PushInner (int firstMove, int lastMove)
16860 int i, j, nrMoves = lastMove - firstMove;
16862 // push current tail of game on stack
16863 savedResult[storedGames] = gameInfo.result;
16864 savedDetails[storedGames] = gameInfo.resultDetails;
16865 gameInfo.resultDetails = NULL;
16866 savedFirst[storedGames] = firstMove;
16867 savedLast [storedGames] = lastMove;
16868 savedFramePtr[storedGames] = framePtr;
16869 framePtr -= nrMoves; // reserve space for the boards
16870 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16871 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16872 for(j=0; j<MOVE_LEN; j++)
16873 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16874 for(j=0; j<2*MOVE_LEN; j++)
16875 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16876 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16877 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16878 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16879 pvInfoList[firstMove+i-1].depth = 0;
16880 commentList[framePtr+i] = commentList[firstMove+i];
16881 commentList[firstMove+i] = NULL;
16885 forwardMostMove = firstMove; // truncate game so we can start variation
16889 PushTail (int firstMove, int lastMove)
16891 if(appData.icsActive) { // only in local mode
16892 forwardMostMove = currentMove; // mimic old ICS behavior
16895 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16897 PushInner(firstMove, lastMove);
16898 if(storedGames == 1) GreyRevert(FALSE);
16899 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16903 PopInner (Boolean annotate)
16906 char buf[8000], moveBuf[20];
16908 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16909 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16910 nrMoves = savedLast[storedGames] - currentMove;
16913 if(!WhiteOnMove(currentMove))
16914 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16915 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16916 for(i=currentMove; i<forwardMostMove; i++) {
16918 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16919 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16920 strcat(buf, moveBuf);
16921 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16922 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16926 for(i=1; i<=nrMoves; i++) { // copy last variation back
16927 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16928 for(j=0; j<MOVE_LEN; j++)
16929 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16930 for(j=0; j<2*MOVE_LEN; j++)
16931 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16932 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16933 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16934 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16935 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16936 commentList[currentMove+i] = commentList[framePtr+i];
16937 commentList[framePtr+i] = NULL;
16939 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16940 framePtr = savedFramePtr[storedGames];
16941 gameInfo.result = savedResult[storedGames];
16942 if(gameInfo.resultDetails != NULL) {
16943 free(gameInfo.resultDetails);
16945 gameInfo.resultDetails = savedDetails[storedGames];
16946 forwardMostMove = currentMove + nrMoves;
16950 PopTail (Boolean annotate)
16952 if(appData.icsActive) return FALSE; // only in local mode
16953 if(!storedGames) return FALSE; // sanity
16954 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16956 PopInner(annotate);
16957 if(currentMove < forwardMostMove) ForwardEvent(); else
16958 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16960 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16966 { // remove all shelved variations
16968 for(i=0; i<storedGames; i++) {
16969 if(savedDetails[i])
16970 free(savedDetails[i]);
16971 savedDetails[i] = NULL;
16973 for(i=framePtr; i<MAX_MOVES; i++) {
16974 if(commentList[i]) free(commentList[i]);
16975 commentList[i] = NULL;
16977 framePtr = MAX_MOVES-1;
16982 LoadVariation (int index, char *text)
16983 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16984 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16985 int level = 0, move;
16987 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16988 // first find outermost bracketing variation
16989 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16990 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16991 if(*p == '{') wait = '}'; else
16992 if(*p == '[') wait = ']'; else
16993 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16994 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16996 if(*p == wait) wait = NULLCHAR; // closing ]} found
16999 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17000 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17001 end[1] = NULLCHAR; // clip off comment beyond variation
17002 ToNrEvent(currentMove-1);
17003 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17004 // kludge: use ParsePV() to append variation to game
17005 move = currentMove;
17006 ParsePV(start, TRUE, TRUE);
17007 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17008 ClearPremoveHighlights();
17010 ToNrEvent(currentMove+1);