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 SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225 void ToggleSecond P((void));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
275 /* States for ics_getting_history */
277 #define H_REQUESTED 1
278 #define H_GOT_REQ_HEADER 2
279 #define H_GOT_UNREQ_HEADER 3
280 #define H_GETTING_MOVES 4
281 #define H_GOT_UNWANTED_HEADER 5
283 /* whosays values for GameEnds */
292 /* Maximum number of games in a cmail message */
293 #define CMAIL_MAX_GAMES 20
295 /* Different types of move when calling RegisterMove */
297 #define CMAIL_RESIGN 1
299 #define CMAIL_ACCEPT 3
301 /* Different types of result to remember for each game */
302 #define CMAIL_NOT_RESULT 0
303 #define CMAIL_OLD_RESULT 1
304 #define CMAIL_NEW_RESULT 2
306 /* Telnet protocol constants */
317 safeStrCpy (char *dst, const char *src, size_t count)
320 assert( dst != NULL );
321 assert( src != NULL );
324 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
325 if( i == count && dst[count-1] != NULLCHAR)
327 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
328 if(appData.debugMode)
329 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335 /* Some compiler can't cast u64 to double
336 * This function do the job for us:
338 * We use the highest bit for cast, this only
339 * works if the highest bit is not
340 * in use (This should not happen)
342 * We used this for all compiler
345 u64ToDouble (u64 value)
348 u64 tmp = value & u64Const(0x7fffffffffffffff);
349 r = (double)(s64)tmp;
350 if (value & u64Const(0x8000000000000000))
351 r += 9.2233720368547758080e18; /* 2^63 */
355 /* Fake up flags for now, as we aren't keeping track of castling
356 availability yet. [HGM] Change of logic: the flag now only
357 indicates the type of castlings allowed by the rule of the game.
358 The actual rights themselves are maintained in the array
359 castlingRights, as part of the game history, and are not probed
365 int flags = F_ALL_CASTLE_OK;
366 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367 switch (gameInfo.variant) {
369 flags &= ~F_ALL_CASTLE_OK;
370 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371 flags |= F_IGNORE_CHECK;
373 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378 case VariantKriegspiel:
379 flags |= F_KRIEGSPIEL_CAPTURE;
381 case VariantCapaRandom:
382 case VariantFischeRandom:
383 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384 case VariantNoCastle:
385 case VariantShatranj:
389 flags &= ~F_ALL_CASTLE_OK;
397 FILE *gameFileFP, *debugFP, *serverFP;
398 char *currentDebugFile; // [HGM] debug split: to remember name
401 [AS] Note: sometimes, the sscanf() function is used to parse the input
402 into a fixed-size buffer. Because of this, we must be prepared to
403 receive strings as long as the size of the input buffer, which is currently
404 set to 4K for Windows and 8K for the rest.
405 So, we must either allocate sufficiently large buffers here, or
406 reduce the size of the input buffer in the input reading part.
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
413 ChessProgramState first, second, pairing;
415 /* premove variables */
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
425 char *ics_prefix = "$";
426 enum ICS_TYPE ics_type = ICS_GENERIC;
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey, controlKey; // [HGM] set by mouse handler
456 int have_sent_ICS_logon = 0;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
460 Boolean adjustedClock;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0, nextGame = 0, roundNr = 0;
465 Boolean waitingForGame = FALSE;
466 TimeMark programStartTime, pauseStart;
467 char ics_handle[MSG_SIZ];
468 int have_set_title = 0;
470 /* animateTraining preserves the state of appData.animate
471 * when Training mode is activated. This allows the
472 * response to be animated when appData.animate == TRUE and
473 * appData.animateDragging == TRUE.
475 Boolean animateTraining;
481 Board boards[MAX_MOVES];
482 /* [HGM] Following 7 needed for accurate legality tests: */
483 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
484 signed char initialRights[BOARD_FILES];
485 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
486 int initialRulePlies, FENrulePlies;
487 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 Boolean shuffleOpenings;
490 int mute; // mute all sounds
492 // [HGM] vari: next 12 to save and restore variations
493 #define MAX_VARIATIONS 10
494 int framePtr = MAX_MOVES-1; // points to free stack entry
496 int savedFirst[MAX_VARIATIONS];
497 int savedLast[MAX_VARIATIONS];
498 int savedFramePtr[MAX_VARIATIONS];
499 char *savedDetails[MAX_VARIATIONS];
500 ChessMove savedResult[MAX_VARIATIONS];
502 void PushTail P((int firstMove, int lastMove));
503 Boolean PopTail P((Boolean annotate));
504 void PushInner P((int firstMove, int lastMove));
505 void PopInner P((Boolean annotate));
506 void CleanupTail P((void));
508 ChessSquare FIDEArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackBishop, BlackKnight, BlackRook }
515 ChessSquare twoKingsArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackKing, BlackKnight, BlackRook }
522 ChessSquare KnightmateArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
524 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
525 { BlackRook, BlackMan, BlackBishop, BlackQueen,
526 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 ChessSquare SpartanArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
533 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
540 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
545 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
547 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
552 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackMan, BlackFerz,
554 BlackKing, BlackMan, BlackKnight, BlackRook }
558 #if (BOARD_FILES>=10)
559 ChessSquare ShogiArray[2][BOARD_FILES] = {
560 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
561 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
562 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
563 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 ChessSquare XiangqiArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
568 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
570 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 ChessSquare CapablancaArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
575 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
577 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 ChessSquare GreatArray[2][BOARD_FILES] = {
581 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
582 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
583 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
584 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 ChessSquare JanusArray[2][BOARD_FILES] = {
588 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
589 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
590 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
591 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 ChessSquare GrandArray[2][BOARD_FILES] = {
595 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
596 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
597 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
598 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
615 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
617 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating (char *str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine (ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions (ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
739 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
740 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
743 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
748 InitEngine (ChessProgramState *cps, int n)
749 { // [HGM] all engine initialiation put in a function that does one engine
753 cps->which = engineNames[n];
754 cps->maybeThinking = FALSE;
758 cps->sendDrawOffers = 1;
760 cps->program = appData.chessProgram[n];
761 cps->host = appData.host[n];
762 cps->dir = appData.directory[n];
763 cps->initString = appData.engInitString[n];
764 cps->computerString = appData.computerString[n];
765 cps->useSigint = TRUE;
766 cps->useSigterm = TRUE;
767 cps->reuse = appData.reuse[n];
768 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
769 cps->useSetboard = FALSE;
771 cps->usePing = FALSE;
774 cps->usePlayother = FALSE;
775 cps->useColors = TRUE;
776 cps->useUsermove = FALSE;
777 cps->sendICS = FALSE;
778 cps->sendName = appData.icsActive;
779 cps->sdKludge = FALSE;
780 cps->stKludge = FALSE;
781 TidyProgramName(cps->program, cps->host, cps->tidy);
783 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
784 cps->analysisSupport = 2; /* detect */
785 cps->analyzing = FALSE;
786 cps->initDone = FALSE;
788 /* New features added by Tord: */
789 cps->useFEN960 = FALSE;
790 cps->useOOCastle = TRUE;
791 /* End of new features added by Tord. */
792 cps->fenOverride = appData.fenOverride[n];
794 /* [HGM] time odds: set factor for each machine */
795 cps->timeOdds = appData.timeOdds[n];
797 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
798 cps->accumulateTC = appData.accumulateTC[n];
799 cps->maxNrOfSessions = 1;
804 cps->supportsNPS = UNKNOWN;
805 cps->memSize = FALSE;
806 cps->maxCores = FALSE;
807 cps->egtFormats[0] = NULLCHAR;
810 cps->optionSettings = appData.engOptions[n];
812 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
813 cps->isUCI = appData.isUCI[n]; /* [AS] */
814 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
816 if (appData.protocolVersion[n] > PROTOVER
817 || appData.protocolVersion[n] < 1)
822 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
823 appData.protocolVersion[n]);
824 if( (len >= MSG_SIZ) && appData.debugMode )
825 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
827 DisplayFatalError(buf, 0, 2);
831 cps->protocolVersion = appData.protocolVersion[n];
834 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
835 ParseFeatures(appData.featureDefaults, cps);
838 ChessProgramState *savCps;
844 if(WaitForEngine(savCps, LoadEngine)) return;
845 CommonEngineInit(); // recalculate time odds
846 if(gameInfo.variant != StringToVariant(appData.variant)) {
847 // we changed variant when loading the engine; this forces us to reset
848 Reset(TRUE, savCps != &first);
849 EditGameEvent(); // for consistency with other path, as Reset changes mode
851 InitChessProgram(savCps, FALSE);
852 SendToProgram("force\n", savCps);
853 DisplayMessage("", "");
854 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
855 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
861 ReplaceEngine (ChessProgramState *cps, int n)
865 appData.noChessProgram = FALSE;
866 appData.clockMode = TRUE;
869 if(n) return; // only startup first engine immediately; second can wait
870 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
875 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877 static char resetOptions[] =
878 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
879 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
880 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
881 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
884 FloatToFront(char **list, char *engineLine)
886 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
888 if(appData.recentEngines <= 0) return;
889 TidyProgramName(engineLine, "localhost", tidy+1);
890 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
891 strncpy(buf+1, *list, MSG_SIZ-50);
892 if(p = strstr(buf, tidy)) { // tidy name appears in list
893 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
894 while(*p++ = *++q); // squeeze out
896 strcat(tidy, buf+1); // put list behind tidy name
897 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
898 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
899 ASSIGN(*list, tidy+1);
902 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
905 Load (ChessProgramState *cps, int i)
907 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
908 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
909 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
910 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
911 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
912 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
913 appData.firstProtocolVersion = PROTOVER;
914 ParseArgsFromString(buf);
916 ReplaceEngine(cps, i);
917 FloatToFront(&appData.recentEngineList, engineLine);
921 while(q = strchr(p, SLASH)) p = q+1;
922 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
923 if(engineDir[0] != NULLCHAR) {
924 ASSIGN(appData.directory[i], engineDir); p = engineName;
925 } else if(p != engineName) { // derive directory from engine path, when not given
927 ASSIGN(appData.directory[i], engineName);
929 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
930 } else { ASSIGN(appData.directory[i], "."); }
932 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
933 snprintf(command, MSG_SIZ, "%s %s", p, params);
936 ASSIGN(appData.chessProgram[i], p);
937 appData.isUCI[i] = isUCI;
938 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
939 appData.hasOwnBookUCI[i] = hasBook;
940 if(!nickName[0]) useNick = FALSE;
941 if(useNick) ASSIGN(appData.pgnName[i], nickName);
945 q = firstChessProgramNames;
946 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
947 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
948 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
949 quote, p, quote, appData.directory[i],
950 useNick ? " -fn \"" : "",
951 useNick ? nickName : "",
953 v1 ? " -firstProtocolVersion 1" : "",
954 hasBook ? "" : " -fNoOwnBookUCI",
955 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
956 storeVariant ? " -variant " : "",
957 storeVariant ? VariantName(gameInfo.variant) : "");
958 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
959 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
960 if(insert != q) insert[-1] = NULLCHAR;
961 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
963 FloatToFront(&appData.recentEngineList, buf);
965 ReplaceEngine(cps, i);
971 int matched, min, sec;
973 * Parse timeControl resource
975 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
976 appData.movesPerSession)) {
978 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
979 DisplayFatalError(buf, 0, 2);
983 * Parse searchTime resource
985 if (*appData.searchTime != NULLCHAR) {
986 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
988 searchTime = min * 60;
989 } else if (matched == 2) {
990 searchTime = min * 60 + sec;
993 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
994 DisplayFatalError(buf, 0, 2);
1003 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1004 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1006 GetTimeMark(&programStartTime);
1007 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1008 appData.seedBase = random() + (random()<<15);
1009 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1011 ClearProgramStats();
1012 programStats.ok_to_send = 1;
1013 programStats.seen_stat = 0;
1016 * Initialize game list
1022 * Internet chess server status
1024 if (appData.icsActive) {
1025 appData.matchMode = FALSE;
1026 appData.matchGames = 0;
1028 appData.noChessProgram = !appData.zippyPlay;
1030 appData.zippyPlay = FALSE;
1031 appData.zippyTalk = FALSE;
1032 appData.noChessProgram = TRUE;
1034 if (*appData.icsHelper != NULLCHAR) {
1035 appData.useTelnet = TRUE;
1036 appData.telnetProgram = appData.icsHelper;
1039 appData.zippyTalk = appData.zippyPlay = FALSE;
1042 /* [AS] Initialize pv info list [HGM] and game state */
1046 for( i=0; i<=framePtr; i++ ) {
1047 pvInfoList[i].depth = -1;
1048 boards[i][EP_STATUS] = EP_NONE;
1049 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1055 /* [AS] Adjudication threshold */
1056 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1058 InitEngine(&first, 0);
1059 InitEngine(&second, 1);
1062 pairing.which = "pairing"; // pairing engine
1063 pairing.pr = NoProc;
1065 pairing.program = appData.pairingEngine;
1066 pairing.host = "localhost";
1069 if (appData.icsActive) {
1070 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1071 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1072 appData.clockMode = FALSE;
1073 first.sendTime = second.sendTime = 0;
1077 /* Override some settings from environment variables, for backward
1078 compatibility. Unfortunately it's not feasible to have the env
1079 vars just set defaults, at least in xboard. Ugh.
1081 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1086 if (!appData.icsActive) {
1090 /* Check for variants that are supported only in ICS mode,
1091 or not at all. Some that are accepted here nevertheless
1092 have bugs; see comments below.
1094 VariantClass variant = StringToVariant(appData.variant);
1096 case VariantBughouse: /* need four players and two boards */
1097 case VariantKriegspiel: /* need to hide pieces and move details */
1098 /* case VariantFischeRandom: (Fabien: moved below) */
1099 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1100 if( (len >= MSG_SIZ) && appData.debugMode )
1101 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1103 DisplayFatalError(buf, 0, 2);
1106 case VariantUnknown:
1107 case VariantLoadable:
1117 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1118 if( (len >= MSG_SIZ) && appData.debugMode )
1119 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121 DisplayFatalError(buf, 0, 2);
1124 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1125 case VariantFairy: /* [HGM] TestLegality definitely off! */
1126 case VariantGothic: /* [HGM] should work */
1127 case VariantCapablanca: /* [HGM] should work */
1128 case VariantCourier: /* [HGM] initial forced moves not implemented */
1129 case VariantShogi: /* [HGM] could still mate with pawn drop */
1130 case VariantKnightmate: /* [HGM] should work */
1131 case VariantCylinder: /* [HGM] untested */
1132 case VariantFalcon: /* [HGM] untested */
1133 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1134 offboard interposition not understood */
1135 case VariantNormal: /* definitely works! */
1136 case VariantWildCastle: /* pieces not automatically shuffled */
1137 case VariantNoCastle: /* pieces not automatically shuffled */
1138 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1139 case VariantLosers: /* should work except for win condition,
1140 and doesn't know captures are mandatory */
1141 case VariantSuicide: /* should work except for win condition,
1142 and doesn't know captures are mandatory */
1143 case VariantGiveaway: /* should work except for win condition,
1144 and doesn't know captures are mandatory */
1145 case VariantTwoKings: /* should work */
1146 case VariantAtomic: /* should work except for win condition */
1147 case Variant3Check: /* should work except for win condition */
1148 case VariantShatranj: /* should work except for all win conditions */
1149 case VariantMakruk: /* should work except for draw countdown */
1150 case VariantBerolina: /* might work if TestLegality is off */
1151 case VariantCapaRandom: /* should work */
1152 case VariantJanus: /* should work */
1153 case VariantSuper: /* experimental */
1154 case VariantGreat: /* experimental, requires legality testing to be off */
1155 case VariantSChess: /* S-Chess, should work */
1156 case VariantGrand: /* should work */
1157 case VariantSpartan: /* should work */
1165 NextIntegerFromString (char ** str, long * value)
1170 while( *s == ' ' || *s == '\t' ) {
1176 if( *s >= '0' && *s <= '9' ) {
1177 while( *s >= '0' && *s <= '9' ) {
1178 *value = *value * 10 + (*s - '0');
1191 NextTimeControlFromString (char ** str, long * value)
1194 int result = NextIntegerFromString( str, &temp );
1197 *value = temp * 60; /* Minutes */
1198 if( **str == ':' ) {
1200 result = NextIntegerFromString( str, &temp );
1201 *value += temp; /* Seconds */
1209 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1210 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1211 int result = -1, type = 0; long temp, temp2;
1213 if(**str != ':') return -1; // old params remain in force!
1215 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1216 if( NextIntegerFromString( str, &temp ) ) return -1;
1217 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1220 /* time only: incremental or sudden-death time control */
1221 if(**str == '+') { /* increment follows; read it */
1223 if(**str == '!') type = *(*str)++; // Bronstein TC
1224 if(result = NextIntegerFromString( str, &temp2)) return -1;
1225 *inc = temp2 * 1000;
1226 if(**str == '.') { // read fraction of increment
1227 char *start = ++(*str);
1228 if(result = NextIntegerFromString( str, &temp2)) return -1;
1230 while(start++ < *str) temp2 /= 10;
1234 *moves = 0; *tc = temp * 1000; *incType = type;
1238 (*str)++; /* classical time control */
1239 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1251 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1252 { /* [HGM] get time to add from the multi-session time-control string */
1253 int incType, moves=1; /* kludge to force reading of first session */
1254 long time, increment;
1257 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1259 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1260 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1261 if(movenr == -1) return time; /* last move before new session */
1262 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1263 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1264 if(!moves) return increment; /* current session is incremental */
1265 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1266 } while(movenr >= -1); /* try again for next session */
1268 return 0; // no new time quota on this move
1272 ParseTimeControl (char *tc, float ti, int mps)
1276 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1279 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1280 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1281 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1287 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1290 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1292 snprintf(buf, MSG_SIZ, ":%s", mytc);
1294 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1296 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1301 /* Parse second time control */
1304 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1312 timeControl_2 = tc2 * 1000;
1322 timeControl = tc1 * 1000;
1325 timeIncrement = ti * 1000; /* convert to ms */
1326 movesPerSession = 0;
1329 movesPerSession = mps;
1337 if (appData.debugMode) {
1338 fprintf(debugFP, "%s\n", programVersion);
1340 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1342 set_cont_sequence(appData.wrapContSeq);
1343 if (appData.matchGames > 0) {
1344 appData.matchMode = TRUE;
1345 } else if (appData.matchMode) {
1346 appData.matchGames = 1;
1348 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1349 appData.matchGames = appData.sameColorGames;
1350 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1351 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1352 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1355 if (appData.noChessProgram || first.protocolVersion == 1) {
1358 /* kludge: allow timeout for initial "feature" commands */
1360 DisplayMessage("", _("Starting chess program"));
1361 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1366 CalculateIndex (int index, int gameNr)
1367 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1369 if(index > 0) return index; // fixed nmber
1370 if(index == 0) return 1;
1371 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1372 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1377 LoadGameOrPosition (int gameNr)
1378 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1379 if (*appData.loadGameFile != NULLCHAR) {
1380 if (!LoadGameFromFile(appData.loadGameFile,
1381 CalculateIndex(appData.loadGameIndex, gameNr),
1382 appData.loadGameFile, FALSE)) {
1383 DisplayFatalError(_("Bad game file"), 0, 1);
1386 } else if (*appData.loadPositionFile != NULLCHAR) {
1387 if (!LoadPositionFromFile(appData.loadPositionFile,
1388 CalculateIndex(appData.loadPositionIndex, gameNr),
1389 appData.loadPositionFile)) {
1390 DisplayFatalError(_("Bad position file"), 0, 1);
1398 ReserveGame (int gameNr, char resChar)
1400 FILE *tf = fopen(appData.tourneyFile, "r+");
1401 char *p, *q, c, buf[MSG_SIZ];
1402 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1403 safeStrCpy(buf, lastMsg, MSG_SIZ);
1404 DisplayMessage(_("Pick new game"), "");
1405 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1406 ParseArgsFromFile(tf);
1407 p = q = appData.results;
1408 if(appData.debugMode) {
1409 char *r = appData.participants;
1410 fprintf(debugFP, "results = '%s'\n", p);
1411 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1412 fprintf(debugFP, "\n");
1414 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1416 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1417 safeStrCpy(q, p, strlen(p) + 2);
1418 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1419 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1420 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1421 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1424 fseek(tf, -(strlen(p)+4), SEEK_END);
1426 if(c != '"') // depending on DOS or Unix line endings we can be one off
1427 fseek(tf, -(strlen(p)+2), SEEK_END);
1428 else fseek(tf, -(strlen(p)+3), SEEK_END);
1429 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1430 DisplayMessage(buf, "");
1431 free(p); appData.results = q;
1432 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1433 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1434 int round = appData.defaultMatchGames * appData.tourneyType;
1435 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1436 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1437 UnloadEngine(&first); // next game belongs to other pairing;
1438 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1440 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 MatchEvent (int mode)
1445 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1447 if(matchMode) { // already in match mode: switch it off
1449 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1452 // if(gameMode != BeginningOfGame) {
1453 // DisplayError(_("You can only start a match from the initial position."), 0);
1457 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1458 /* Set up machine vs. machine match */
1460 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1461 if(appData.tourneyFile[0]) {
1463 if(nextGame > appData.matchGames) {
1465 if(strchr(appData.results, '*') == NULL) {
1467 appData.tourneyCycles++;
1468 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1470 NextTourneyGame(-1, &dummy);
1472 if(nextGame <= appData.matchGames) {
1473 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1475 ScheduleDelayedEvent(NextMatchGame, 10000);
1480 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1481 DisplayError(buf, 0);
1482 appData.tourneyFile[0] = 0;
1486 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1487 DisplayFatalError(_("Can't have a match with no chess programs"),
1492 matchGame = roundNr = 1;
1493 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1500 InitBackEnd3 P((void))
1502 GameMode initialMode;
1506 InitChessProgram(&first, startedFromSetupPosition);
1508 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1509 free(programVersion);
1510 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1511 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1512 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1515 if (appData.icsActive) {
1517 /* [DM] Make a console window if needed [HGM] merged ifs */
1523 if (*appData.icsCommPort != NULLCHAR)
1524 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1525 appData.icsCommPort);
1527 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1528 appData.icsHost, appData.icsPort);
1530 if( (len >= MSG_SIZ) && appData.debugMode )
1531 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533 DisplayFatalError(buf, err, 1);
1538 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1540 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1541 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1542 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1543 } else if (appData.noChessProgram) {
1549 if (*appData.cmailGameName != NULLCHAR) {
1551 OpenLoopback(&cmailPR);
1553 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557 DisplayMessage("", "");
1558 if (StrCaseCmp(appData.initialMode, "") == 0) {
1559 initialMode = BeginningOfGame;
1560 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1561 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1562 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1563 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1566 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1567 initialMode = TwoMachinesPlay;
1568 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1569 initialMode = AnalyzeFile;
1570 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1571 initialMode = AnalyzeMode;
1572 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1573 initialMode = MachinePlaysWhite;
1574 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1575 initialMode = MachinePlaysBlack;
1576 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1577 initialMode = EditGame;
1578 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1579 initialMode = EditPosition;
1580 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1581 initialMode = Training;
1583 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1584 if( (len >= MSG_SIZ) && appData.debugMode )
1585 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1587 DisplayFatalError(buf, 0, 2);
1591 if (appData.matchMode) {
1592 if(appData.tourneyFile[0]) { // start tourney from command line
1594 if(f = fopen(appData.tourneyFile, "r")) {
1595 ParseArgsFromFile(f); // make sure tourney parmeters re known
1597 appData.clockMode = TRUE;
1599 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1602 } else if (*appData.cmailGameName != NULLCHAR) {
1603 /* Set up cmail mode */
1604 ReloadCmailMsgEvent(TRUE);
1606 /* Set up other modes */
1607 if (initialMode == AnalyzeFile) {
1608 if (*appData.loadGameFile == NULLCHAR) {
1609 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613 if (*appData.loadGameFile != NULLCHAR) {
1614 (void) LoadGameFromFile(appData.loadGameFile,
1615 appData.loadGameIndex,
1616 appData.loadGameFile, TRUE);
1617 } else if (*appData.loadPositionFile != NULLCHAR) {
1618 (void) LoadPositionFromFile(appData.loadPositionFile,
1619 appData.loadPositionIndex,
1620 appData.loadPositionFile);
1621 /* [HGM] try to make self-starting even after FEN load */
1622 /* to allow automatic setup of fairy variants with wtm */
1623 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1624 gameMode = BeginningOfGame;
1625 setboardSpoiledMachineBlack = 1;
1627 /* [HGM] loadPos: make that every new game uses the setup */
1628 /* from file as long as we do not switch variant */
1629 if(!blackPlaysFirst) {
1630 startedFromPositionFile = TRUE;
1631 CopyBoard(filePosition, boards[0]);
1634 if (initialMode == AnalyzeMode) {
1635 if (appData.noChessProgram) {
1636 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1639 if (appData.icsActive) {
1640 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644 } else if (initialMode == AnalyzeFile) {
1645 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1646 ShowThinkingEvent();
1648 AnalysisPeriodicEvent(1);
1649 } else if (initialMode == MachinePlaysWhite) {
1650 if (appData.noChessProgram) {
1651 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655 if (appData.icsActive) {
1656 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660 MachineWhiteEvent();
1661 } else if (initialMode == MachinePlaysBlack) {
1662 if (appData.noChessProgram) {
1663 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667 if (appData.icsActive) {
1668 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672 MachineBlackEvent();
1673 } else if (initialMode == TwoMachinesPlay) {
1674 if (appData.noChessProgram) {
1675 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679 if (appData.icsActive) {
1680 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1685 } else if (initialMode == EditGame) {
1687 } else if (initialMode == EditPosition) {
1688 EditPositionEvent();
1689 } else if (initialMode == Training) {
1690 if (*appData.loadGameFile == NULLCHAR) {
1691 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1700 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1702 DisplayBook(current+1);
1704 MoveHistorySet( movelist, first, last, current, pvInfoList );
1706 EvalGraphSet( first, last, current, pvInfoList );
1708 MakeEngineOutputTitle();
1712 * Establish will establish a contact to a remote host.port.
1713 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1714 * used to talk to the host.
1715 * Returns 0 if okay, error code if not.
1722 if (*appData.icsCommPort != NULLCHAR) {
1723 /* Talk to the host through a serial comm port */
1724 return OpenCommPort(appData.icsCommPort, &icsPR);
1726 } else if (*appData.gateway != NULLCHAR) {
1727 if (*appData.remoteShell == NULLCHAR) {
1728 /* Use the rcmd protocol to run telnet program on a gateway host */
1729 snprintf(buf, sizeof(buf), "%s %s %s",
1730 appData.telnetProgram, appData.icsHost, appData.icsPort);
1731 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1734 /* Use the rsh program to run telnet program on a gateway host */
1735 if (*appData.remoteUser == NULLCHAR) {
1736 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1737 appData.gateway, appData.telnetProgram,
1738 appData.icsHost, appData.icsPort);
1740 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1741 appData.remoteShell, appData.gateway,
1742 appData.remoteUser, appData.telnetProgram,
1743 appData.icsHost, appData.icsPort);
1745 return StartChildProcess(buf, "", &icsPR);
1748 } else if (appData.useTelnet) {
1749 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1752 /* TCP socket interface differs somewhat between
1753 Unix and NT; handle details in the front end.
1755 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1760 EscapeExpand (char *p, char *q)
1761 { // [HGM] initstring: routine to shape up string arguments
1762 while(*p++ = *q++) if(p[-1] == '\\')
1764 case 'n': p[-1] = '\n'; break;
1765 case 'r': p[-1] = '\r'; break;
1766 case 't': p[-1] = '\t'; break;
1767 case '\\': p[-1] = '\\'; break;
1768 case 0: *p = 0; return;
1769 default: p[-1] = q[-1]; break;
1774 show_bytes (FILE *fp, char *buf, int count)
1777 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1778 fprintf(fp, "\\%03o", *buf & 0xff);
1787 /* Returns an errno value */
1789 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1791 char buf[8192], *p, *q, *buflim;
1792 int left, newcount, outcount;
1794 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1795 *appData.gateway != NULLCHAR) {
1796 if (appData.debugMode) {
1797 fprintf(debugFP, ">ICS: ");
1798 show_bytes(debugFP, message, count);
1799 fprintf(debugFP, "\n");
1801 return OutputToProcess(pr, message, count, outError);
1804 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1811 if (appData.debugMode) {
1812 fprintf(debugFP, ">ICS: ");
1813 show_bytes(debugFP, buf, newcount);
1814 fprintf(debugFP, "\n");
1816 outcount = OutputToProcess(pr, buf, newcount, outError);
1817 if (outcount < newcount) return -1; /* to be sure */
1824 } else if (((unsigned char) *p) == TN_IAC) {
1825 *q++ = (char) TN_IAC;
1832 if (appData.debugMode) {
1833 fprintf(debugFP, ">ICS: ");
1834 show_bytes(debugFP, buf, newcount);
1835 fprintf(debugFP, "\n");
1837 outcount = OutputToProcess(pr, buf, newcount, outError);
1838 if (outcount < newcount) return -1; /* to be sure */
1843 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1845 int outError, outCount;
1846 static int gotEof = 0;
1848 /* Pass data read from player on to ICS */
1851 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1852 if (outCount < count) {
1853 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855 } else if (count < 0) {
1856 RemoveInputSource(isr);
1857 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1858 } else if (gotEof++ > 0) {
1859 RemoveInputSource(isr);
1860 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1866 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1867 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1868 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1869 SendToICS("date\n");
1870 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1873 /* added routine for printf style output to ics */
1875 ics_printf (char *format, ...)
1877 char buffer[MSG_SIZ];
1880 va_start(args, format);
1881 vsnprintf(buffer, sizeof(buffer), format, args);
1882 buffer[sizeof(buffer)-1] = '\0';
1890 int count, outCount, outError;
1892 if (icsPR == NoProc) return;
1895 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1896 if (outCount < count) {
1897 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 /* This is used for sending logon scripts to the ICS. Sending
1902 without a delay causes problems when using timestamp on ICC
1903 (at least on my machine). */
1905 SendToICSDelayed (char *s, long msdelay)
1907 int count, outCount, outError;
1909 if (icsPR == NoProc) return;
1912 if (appData.debugMode) {
1913 fprintf(debugFP, ">ICS: ");
1914 show_bytes(debugFP, s, count);
1915 fprintf(debugFP, "\n");
1917 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1919 if (outCount < count) {
1920 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1925 /* Remove all highlighting escape sequences in s
1926 Also deletes any suffix starting with '('
1929 StripHighlightAndTitle (char *s)
1931 static char retbuf[MSG_SIZ];
1934 while (*s != NULLCHAR) {
1935 while (*s == '\033') {
1936 while (*s != NULLCHAR && !isalpha(*s)) s++;
1937 if (*s != NULLCHAR) s++;
1939 while (*s != NULLCHAR && *s != '\033') {
1940 if (*s == '(' || *s == '[') {
1951 /* Remove all highlighting escape sequences in s */
1953 StripHighlight (char *s)
1955 static char retbuf[MSG_SIZ];
1958 while (*s != NULLCHAR) {
1959 while (*s == '\033') {
1960 while (*s != NULLCHAR && !isalpha(*s)) s++;
1961 if (*s != NULLCHAR) s++;
1963 while (*s != NULLCHAR && *s != '\033') {
1971 char *variantNames[] = VARIANT_NAMES;
1973 VariantName (VariantClass v)
1975 return variantNames[v];
1979 /* Identify a variant from the strings the chess servers use or the
1980 PGN Variant tag names we use. */
1982 StringToVariant (char *e)
1986 VariantClass v = VariantNormal;
1987 int i, found = FALSE;
1993 /* [HGM] skip over optional board-size prefixes */
1994 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1995 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1996 while( *e++ != '_');
1999 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2003 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2004 if (StrCaseStr(e, variantNames[i])) {
2005 v = (VariantClass) i;
2012 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2013 || StrCaseStr(e, "wild/fr")
2014 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2015 v = VariantFischeRandom;
2016 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2017 (i = 1, p = StrCaseStr(e, "w"))) {
2019 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2026 case 0: /* FICS only, actually */
2028 /* Castling legal even if K starts on d-file */
2029 v = VariantWildCastle;
2034 /* Castling illegal even if K & R happen to start in
2035 normal positions. */
2036 v = VariantNoCastle;
2049 /* Castling legal iff K & R start in normal positions */
2055 /* Special wilds for position setup; unclear what to do here */
2056 v = VariantLoadable;
2059 /* Bizarre ICC game */
2060 v = VariantTwoKings;
2063 v = VariantKriegspiel;
2069 v = VariantFischeRandom;
2072 v = VariantCrazyhouse;
2075 v = VariantBughouse;
2081 /* Not quite the same as FICS suicide! */
2082 v = VariantGiveaway;
2088 v = VariantShatranj;
2091 /* Temporary names for future ICC types. The name *will* change in
2092 the next xboard/WinBoard release after ICC defines it. */
2130 v = VariantCapablanca;
2133 v = VariantKnightmate;
2139 v = VariantCylinder;
2145 v = VariantCapaRandom;
2148 v = VariantBerolina;
2160 /* Found "wild" or "w" in the string but no number;
2161 must assume it's normal chess. */
2165 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2166 if( (len >= MSG_SIZ) && appData.debugMode )
2167 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2169 DisplayError(buf, 0);
2175 if (appData.debugMode) {
2176 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2177 e, wnum, VariantName(v));
2182 static int leftover_start = 0, leftover_len = 0;
2183 char star_match[STAR_MATCH_N][MSG_SIZ];
2185 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2186 advance *index beyond it, and set leftover_start to the new value of
2187 *index; else return FALSE. If pattern contains the character '*', it
2188 matches any sequence of characters not containing '\r', '\n', or the
2189 character following the '*' (if any), and the matched sequence(s) are
2190 copied into star_match.
2193 looking_at ( char *buf, int *index, char *pattern)
2195 char *bufp = &buf[*index], *patternp = pattern;
2197 char *matchp = star_match[0];
2200 if (*patternp == NULLCHAR) {
2201 *index = leftover_start = bufp - buf;
2205 if (*bufp == NULLCHAR) return FALSE;
2206 if (*patternp == '*') {
2207 if (*bufp == *(patternp + 1)) {
2209 matchp = star_match[++star_count];
2213 } else if (*bufp == '\n' || *bufp == '\r') {
2215 if (*patternp == NULLCHAR)
2220 *matchp++ = *bufp++;
2224 if (*patternp != *bufp) return FALSE;
2231 SendToPlayer (char *data, int length)
2233 int error, outCount;
2234 outCount = OutputToProcess(NoProc, data, length, &error);
2235 if (outCount < length) {
2236 DisplayFatalError(_("Error writing to display"), error, 1);
2241 PackHolding (char packed[], char *holding)
2251 switch (runlength) {
2262 sprintf(q, "%d", runlength);
2274 /* Telnet protocol requests from the front end */
2276 TelnetRequest (unsigned char ddww, unsigned char option)
2278 unsigned char msg[3];
2279 int outCount, outError;
2281 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2283 if (appData.debugMode) {
2284 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2309 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2312 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2317 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2319 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2326 if (!appData.icsActive) return;
2327 TelnetRequest(TN_DO, TN_ECHO);
2333 if (!appData.icsActive) return;
2334 TelnetRequest(TN_DONT, TN_ECHO);
2338 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2340 /* put the holdings sent to us by the server on the board holdings area */
2341 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2345 if(gameInfo.holdingsWidth < 2) return;
2346 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2347 return; // prevent overwriting by pre-board holdings
2349 if( (int)lowestPiece >= BlackPawn ) {
2352 holdingsStartRow = BOARD_HEIGHT-1;
2355 holdingsColumn = BOARD_WIDTH-1;
2356 countsColumn = BOARD_WIDTH-2;
2357 holdingsStartRow = 0;
2361 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2362 board[i][holdingsColumn] = EmptySquare;
2363 board[i][countsColumn] = (ChessSquare) 0;
2365 while( (p=*holdings++) != NULLCHAR ) {
2366 piece = CharToPiece( ToUpper(p) );
2367 if(piece == EmptySquare) continue;
2368 /*j = (int) piece - (int) WhitePawn;*/
2369 j = PieceToNumber(piece);
2370 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2371 if(j < 0) continue; /* should not happen */
2372 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2373 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2374 board[holdingsStartRow+j*direction][countsColumn]++;
2380 VariantSwitch (Board board, VariantClass newVariant)
2382 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2383 static Board oldBoard;
2385 startedFromPositionFile = FALSE;
2386 if(gameInfo.variant == newVariant) return;
2388 /* [HGM] This routine is called each time an assignment is made to
2389 * gameInfo.variant during a game, to make sure the board sizes
2390 * are set to match the new variant. If that means adding or deleting
2391 * holdings, we shift the playing board accordingly
2392 * This kludge is needed because in ICS observe mode, we get boards
2393 * of an ongoing game without knowing the variant, and learn about the
2394 * latter only later. This can be because of the move list we requested,
2395 * in which case the game history is refilled from the beginning anyway,
2396 * but also when receiving holdings of a crazyhouse game. In the latter
2397 * case we want to add those holdings to the already received position.
2401 if (appData.debugMode) {
2402 fprintf(debugFP, "Switch board from %s to %s\n",
2403 VariantName(gameInfo.variant), VariantName(newVariant));
2404 setbuf(debugFP, NULL);
2406 shuffleOpenings = 0; /* [HGM] shuffle */
2407 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2411 newWidth = 9; newHeight = 9;
2412 gameInfo.holdingsSize = 7;
2413 case VariantBughouse:
2414 case VariantCrazyhouse:
2415 newHoldingsWidth = 2; break;
2419 newHoldingsWidth = 2;
2420 gameInfo.holdingsSize = 8;
2423 case VariantCapablanca:
2424 case VariantCapaRandom:
2427 newHoldingsWidth = gameInfo.holdingsSize = 0;
2430 if(newWidth != gameInfo.boardWidth ||
2431 newHeight != gameInfo.boardHeight ||
2432 newHoldingsWidth != gameInfo.holdingsWidth ) {
2434 /* shift position to new playing area, if needed */
2435 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2436 for(i=0; i<BOARD_HEIGHT; i++)
2437 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2438 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440 for(i=0; i<newHeight; i++) {
2441 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2442 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2444 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2445 for(i=0; i<BOARD_HEIGHT; i++)
2446 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2447 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2450 board[HOLDINGS_SET] = 0;
2451 gameInfo.boardWidth = newWidth;
2452 gameInfo.boardHeight = newHeight;
2453 gameInfo.holdingsWidth = newHoldingsWidth;
2454 gameInfo.variant = newVariant;
2455 InitDrawingSizes(-2, 0);
2456 } else gameInfo.variant = newVariant;
2457 CopyBoard(oldBoard, board); // remember correctly formatted board
2458 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2459 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2462 static int loggedOn = FALSE;
2464 /*-- Game start info cache: --*/
2466 char gs_kind[MSG_SIZ];
2467 static char player1Name[128] = "";
2468 static char player2Name[128] = "";
2469 static char cont_seq[] = "\n\\ ";
2470 static int player1Rating = -1;
2471 static int player2Rating = -1;
2472 /*----------------------------*/
2474 ColorClass curColor = ColorNormal;
2475 int suppressKibitz = 0;
2478 Boolean soughtPending = FALSE;
2479 Boolean seekGraphUp;
2480 #define MAX_SEEK_ADS 200
2482 char *seekAdList[MAX_SEEK_ADS];
2483 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2484 float tcList[MAX_SEEK_ADS];
2485 char colorList[MAX_SEEK_ADS];
2486 int nrOfSeekAds = 0;
2487 int minRating = 1010, maxRating = 2800;
2488 int hMargin = 10, vMargin = 20, h, w;
2489 extern int squareSize, lineGap;
2494 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2495 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2496 if(r < minRating+100 && r >=0 ) r = minRating+100;
2497 if(r > maxRating) r = maxRating;
2498 if(tc < 1.f) tc = 1.f;
2499 if(tc > 95.f) tc = 95.f;
2500 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2501 y = ((double)r - minRating)/(maxRating - minRating)
2502 * (h-vMargin-squareSize/8-1) + vMargin;
2503 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2504 if(strstr(seekAdList[i], " u ")) color = 1;
2505 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2506 !strstr(seekAdList[i], "bullet") &&
2507 !strstr(seekAdList[i], "blitz") &&
2508 !strstr(seekAdList[i], "standard") ) color = 2;
2509 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2510 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2514 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2516 char buf[MSG_SIZ], *ext = "";
2517 VariantClass v = StringToVariant(type);
2518 if(strstr(type, "wild")) {
2519 ext = type + 4; // append wild number
2520 if(v == VariantFischeRandom) type = "chess960"; else
2521 if(v == VariantLoadable) type = "setup"; else
2522 type = VariantName(v);
2524 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2525 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2526 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2527 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2528 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2529 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2530 seekNrList[nrOfSeekAds] = nr;
2531 zList[nrOfSeekAds] = 0;
2532 seekAdList[nrOfSeekAds++] = StrSave(buf);
2533 if(plot) PlotSeekAd(nrOfSeekAds-1);
2538 EraseSeekDot (int i)
2540 int x = xList[i], y = yList[i], d=squareSize/4, k;
2541 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2542 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2543 // now replot every dot that overlapped
2544 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2545 int xx = xList[k], yy = yList[k];
2546 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2547 DrawSeekDot(xx, yy, colorList[k]);
2552 RemoveSeekAd (int nr)
2555 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2557 if(seekAdList[i]) free(seekAdList[i]);
2558 seekAdList[i] = seekAdList[--nrOfSeekAds];
2559 seekNrList[i] = seekNrList[nrOfSeekAds];
2560 ratingList[i] = ratingList[nrOfSeekAds];
2561 colorList[i] = colorList[nrOfSeekAds];
2562 tcList[i] = tcList[nrOfSeekAds];
2563 xList[i] = xList[nrOfSeekAds];
2564 yList[i] = yList[nrOfSeekAds];
2565 zList[i] = zList[nrOfSeekAds];
2566 seekAdList[nrOfSeekAds] = NULL;
2572 MatchSoughtLine (char *line)
2574 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2575 int nr, base, inc, u=0; char dummy;
2577 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2578 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2580 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2581 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2582 // match: compact and save the line
2583 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2593 if(!seekGraphUp) return FALSE;
2594 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2595 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2597 DrawSeekBackground(0, 0, w, h);
2598 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2599 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2600 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2601 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2603 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2606 snprintf(buf, MSG_SIZ, "%d", i);
2607 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2610 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2611 for(i=1; i<100; i+=(i<10?1:5)) {
2612 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2613 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2614 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2616 snprintf(buf, MSG_SIZ, "%d", i);
2617 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2620 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2625 SeekGraphClick (ClickType click, int x, int y, int moving)
2627 static int lastDown = 0, displayed = 0, lastSecond;
2628 if(y < 0) return FALSE;
2629 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2630 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2631 if(!seekGraphUp) return FALSE;
2632 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2633 DrawPosition(TRUE, NULL);
2636 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2637 if(click == Release || moving) return FALSE;
2639 soughtPending = TRUE;
2640 SendToICS(ics_prefix);
2641 SendToICS("sought\n"); // should this be "sought all"?
2642 } else { // issue challenge based on clicked ad
2643 int dist = 10000; int i, closest = 0, second = 0;
2644 for(i=0; i<nrOfSeekAds; i++) {
2645 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2646 if(d < dist) { dist = d; closest = i; }
2647 second += (d - zList[i] < 120); // count in-range ads
2648 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2652 second = (second > 1);
2653 if(displayed != closest || second != lastSecond) {
2654 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2655 lastSecond = second; displayed = closest;
2657 if(click == Press) {
2658 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2661 } // on press 'hit', only show info
2662 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2663 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2664 SendToICS(ics_prefix);
2666 return TRUE; // let incoming board of started game pop down the graph
2667 } else if(click == Release) { // release 'miss' is ignored
2668 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2669 if(moving == 2) { // right up-click
2670 nrOfSeekAds = 0; // refresh graph
2671 soughtPending = TRUE;
2672 SendToICS(ics_prefix);
2673 SendToICS("sought\n"); // should this be "sought all"?
2676 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2677 // press miss or release hit 'pop down' seek graph
2678 seekGraphUp = FALSE;
2679 DrawPosition(TRUE, NULL);
2685 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2687 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2688 #define STARTED_NONE 0
2689 #define STARTED_MOVES 1
2690 #define STARTED_BOARD 2
2691 #define STARTED_OBSERVE 3
2692 #define STARTED_HOLDINGS 4
2693 #define STARTED_CHATTER 5
2694 #define STARTED_COMMENT 6
2695 #define STARTED_MOVES_NOHIDE 7
2697 static int started = STARTED_NONE;
2698 static char parse[20000];
2699 static int parse_pos = 0;
2700 static char buf[BUF_SIZE + 1];
2701 static int firstTime = TRUE, intfSet = FALSE;
2702 static ColorClass prevColor = ColorNormal;
2703 static int savingComment = FALSE;
2704 static int cmatch = 0; // continuation sequence match
2711 int backup; /* [DM] For zippy color lines */
2713 char talker[MSG_SIZ]; // [HGM] chat
2716 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2718 if (appData.debugMode) {
2720 fprintf(debugFP, "<ICS: ");
2721 show_bytes(debugFP, data, count);
2722 fprintf(debugFP, "\n");
2726 if (appData.debugMode) { int f = forwardMostMove;
2727 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2728 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2729 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2732 /* If last read ended with a partial line that we couldn't parse,
2733 prepend it to the new read and try again. */
2734 if (leftover_len > 0) {
2735 for (i=0; i<leftover_len; i++)
2736 buf[i] = buf[leftover_start + i];
2739 /* copy new characters into the buffer */
2740 bp = buf + leftover_len;
2741 buf_len=leftover_len;
2742 for (i=0; i<count; i++)
2745 if (data[i] == '\r')
2748 // join lines split by ICS?
2749 if (!appData.noJoin)
2752 Joining just consists of finding matches against the
2753 continuation sequence, and discarding that sequence
2754 if found instead of copying it. So, until a match
2755 fails, there's nothing to do since it might be the
2756 complete sequence, and thus, something we don't want
2759 if (data[i] == cont_seq[cmatch])
2762 if (cmatch == strlen(cont_seq))
2764 cmatch = 0; // complete match. just reset the counter
2767 it's possible for the ICS to not include the space
2768 at the end of the last word, making our [correct]
2769 join operation fuse two separate words. the server
2770 does this when the space occurs at the width setting.
2772 if (!buf_len || buf[buf_len-1] != ' ')
2783 match failed, so we have to copy what matched before
2784 falling through and copying this character. In reality,
2785 this will only ever be just the newline character, but
2786 it doesn't hurt to be precise.
2788 strncpy(bp, cont_seq, cmatch);
2800 buf[buf_len] = NULLCHAR;
2801 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2806 while (i < buf_len) {
2807 /* Deal with part of the TELNET option negotiation
2808 protocol. We refuse to do anything beyond the
2809 defaults, except that we allow the WILL ECHO option,
2810 which ICS uses to turn off password echoing when we are
2811 directly connected to it. We reject this option
2812 if localLineEditing mode is on (always on in xboard)
2813 and we are talking to port 23, which might be a real
2814 telnet server that will try to keep WILL ECHO on permanently.
2816 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2817 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2818 unsigned char option;
2820 switch ((unsigned char) buf[++i]) {
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<WILL ");
2824 switch (option = (unsigned char) buf[++i]) {
2826 if (appData.debugMode)
2827 fprintf(debugFP, "ECHO ");
2828 /* Reply only if this is a change, according
2829 to the protocol rules. */
2830 if (remoteEchoOption) break;
2831 if (appData.localLineEditing &&
2832 atoi(appData.icsPort) == TN_PORT) {
2833 TelnetRequest(TN_DONT, TN_ECHO);
2836 TelnetRequest(TN_DO, TN_ECHO);
2837 remoteEchoOption = TRUE;
2841 if (appData.debugMode)
2842 fprintf(debugFP, "%d ", option);
2843 /* Whatever this is, we don't want it. */
2844 TelnetRequest(TN_DONT, option);
2849 if (appData.debugMode)
2850 fprintf(debugFP, "\n<WONT ");
2851 switch (option = (unsigned char) buf[++i]) {
2853 if (appData.debugMode)
2854 fprintf(debugFP, "ECHO ");
2855 /* Reply only if this is a change, according
2856 to the protocol rules. */
2857 if (!remoteEchoOption) break;
2859 TelnetRequest(TN_DONT, TN_ECHO);
2860 remoteEchoOption = FALSE;
2863 if (appData.debugMode)
2864 fprintf(debugFP, "%d ", (unsigned char) option);
2865 /* Whatever this is, it must already be turned
2866 off, because we never agree to turn on
2867 anything non-default, so according to the
2868 protocol rules, we don't reply. */
2873 if (appData.debugMode)
2874 fprintf(debugFP, "\n<DO ");
2875 switch (option = (unsigned char) buf[++i]) {
2877 /* Whatever this is, we refuse to do it. */
2878 if (appData.debugMode)
2879 fprintf(debugFP, "%d ", option);
2880 TelnetRequest(TN_WONT, option);
2885 if (appData.debugMode)
2886 fprintf(debugFP, "\n<DONT ");
2887 switch (option = (unsigned char) buf[++i]) {
2889 if (appData.debugMode)
2890 fprintf(debugFP, "%d ", option);
2891 /* Whatever this is, we are already not doing
2892 it, because we never agree to do anything
2893 non-default, so according to the protocol
2894 rules, we don't reply. */
2899 if (appData.debugMode)
2900 fprintf(debugFP, "\n<IAC ");
2901 /* Doubled IAC; pass it through */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2907 /* Drop all other telnet commands on the floor */
2910 if (oldi > next_out)
2911 SendToPlayer(&buf[next_out], oldi - next_out);
2917 /* OK, this at least will *usually* work */
2918 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2922 if (loggedOn && !intfSet) {
2923 if (ics_type == ICS_ICC) {
2924 snprintf(str, MSG_SIZ,
2925 "/set-quietly interface %s\n/set-quietly style 12\n",
2927 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928 strcat(str, "/set-2 51 1\n/set seek 1\n");
2929 } else if (ics_type == ICS_CHESSNET) {
2930 snprintf(str, MSG_SIZ, "/style 12\n");
2932 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2933 strcat(str, programVersion);
2934 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2935 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2936 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2938 strcat(str, "$iset nohighlight 1\n");
2940 strcat(str, "$iset lock 1\n$style 12\n");
2943 NotifyFrontendLogin();
2947 if (started == STARTED_COMMENT) {
2948 /* Accumulate characters in comment */
2949 parse[parse_pos++] = buf[i];
2950 if (buf[i] == '\n') {
2951 parse[parse_pos] = NULLCHAR;
2952 if(chattingPartner>=0) {
2954 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2955 OutputChatMessage(chattingPartner, mess);
2956 chattingPartner = -1;
2957 next_out = i+1; // [HGM] suppress printing in ICS window
2959 if(!suppressKibitz) // [HGM] kibitz
2960 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2961 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2962 int nrDigit = 0, nrAlph = 0, j;
2963 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2964 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2965 parse[parse_pos] = NULLCHAR;
2966 // try to be smart: if it does not look like search info, it should go to
2967 // ICS interaction window after all, not to engine-output window.
2968 for(j=0; j<parse_pos; j++) { // count letters and digits
2969 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2970 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2971 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2973 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2974 int depth=0; float score;
2975 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2976 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2977 pvInfoList[forwardMostMove-1].depth = depth;
2978 pvInfoList[forwardMostMove-1].score = 100*score;
2980 OutputKibitz(suppressKibitz, parse);
2983 if(gameMode == IcsObserving) // restore original ICS messages
2984 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2986 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2987 SendToPlayer(tmp, strlen(tmp));
2989 next_out = i+1; // [HGM] suppress printing in ICS window
2991 started = STARTED_NONE;
2993 /* Don't match patterns against characters in comment */
2998 if (started == STARTED_CHATTER) {
2999 if (buf[i] != '\n') {
3000 /* Don't match patterns against characters in chatter */
3004 started = STARTED_NONE;
3005 if(suppressKibitz) next_out = i+1;
3008 /* Kludge to deal with rcmd protocol */
3009 if (firstTime && looking_at(buf, &i, "\001*")) {
3010 DisplayFatalError(&buf[1], 0, 1);
3016 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3019 if (appData.debugMode)
3020 fprintf(debugFP, "ics_type %d\n", ics_type);
3023 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3024 ics_type = ICS_FICS;
3026 if (appData.debugMode)
3027 fprintf(debugFP, "ics_type %d\n", ics_type);
3030 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3031 ics_type = ICS_CHESSNET;
3033 if (appData.debugMode)
3034 fprintf(debugFP, "ics_type %d\n", ics_type);
3039 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3040 looking_at(buf, &i, "Logging you in as \"*\"") ||
3041 looking_at(buf, &i, "will be \"*\""))) {
3042 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3046 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3048 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3049 DisplayIcsInteractionTitle(buf);
3050 have_set_title = TRUE;
3053 /* skip finger notes */
3054 if (started == STARTED_NONE &&
3055 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3056 (buf[i] == '1' && buf[i+1] == '0')) &&
3057 buf[i+2] == ':' && buf[i+3] == ' ') {
3058 started = STARTED_CHATTER;
3064 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3065 if(appData.seekGraph) {
3066 if(soughtPending && MatchSoughtLine(buf+i)) {
3067 i = strstr(buf+i, "rated") - buf;
3068 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069 next_out = leftover_start = i;
3070 started = STARTED_CHATTER;
3071 suppressKibitz = TRUE;
3074 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3075 && looking_at(buf, &i, "* ads displayed")) {
3076 soughtPending = FALSE;
3081 if(appData.autoRefresh) {
3082 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3083 int s = (ics_type == ICS_ICC); // ICC format differs
3085 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3086 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3087 looking_at(buf, &i, "*% "); // eat prompt
3088 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3089 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090 next_out = i; // suppress
3093 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3094 char *p = star_match[0];
3096 if(seekGraphUp) RemoveSeekAd(atoi(p));
3097 while(*p && *p++ != ' '); // next
3099 looking_at(buf, &i, "*% "); // eat prompt
3100 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107 /* skip formula vars */
3108 if (started == STARTED_NONE &&
3109 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3110 started = STARTED_CHATTER;
3115 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3116 if (appData.autoKibitz && started == STARTED_NONE &&
3117 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3118 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3119 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3120 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3121 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3122 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3123 suppressKibitz = TRUE;
3124 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3126 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3127 && (gameMode == IcsPlayingWhite)) ||
3128 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3129 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3130 started = STARTED_CHATTER; // own kibitz we simply discard
3132 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3133 parse_pos = 0; parse[0] = NULLCHAR;
3134 savingComment = TRUE;
3135 suppressKibitz = gameMode != IcsObserving ? 2 :
3136 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3140 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3141 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3142 && atoi(star_match[0])) {
3143 // suppress the acknowledgements of our own autoKibitz
3145 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3147 SendToPlayer(star_match[0], strlen(star_match[0]));
3148 if(looking_at(buf, &i, "*% ")) // eat prompt
3149 suppressKibitz = FALSE;
3153 } // [HGM] kibitz: end of patch
3155 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3157 // [HGM] chat: intercept tells by users for which we have an open chat window
3159 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3160 looking_at(buf, &i, "* whispers:") ||
3161 looking_at(buf, &i, "* kibitzes:") ||
3162 looking_at(buf, &i, "* shouts:") ||
3163 looking_at(buf, &i, "* c-shouts:") ||
3164 looking_at(buf, &i, "--> * ") ||
3165 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3167 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3168 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3170 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3171 chattingPartner = -1;
3173 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3174 for(p=0; p<MAX_CHAT; p++) {
3175 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3176 talker[0] = '['; strcat(talker, "] ");
3177 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3178 chattingPartner = p; break;
3181 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3182 for(p=0; p<MAX_CHAT; p++) {
3183 if(!strcmp("kibitzes", chatPartner[p])) {
3184 talker[0] = '['; strcat(talker, "] ");
3185 chattingPartner = p; break;
3188 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3189 for(p=0; p<MAX_CHAT; p++) {
3190 if(!strcmp("whispers", chatPartner[p])) {
3191 talker[0] = '['; strcat(talker, "] ");
3192 chattingPartner = p; break;
3195 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3196 if(buf[i-8] == '-' && buf[i-3] == 't')
3197 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3198 if(!strcmp("c-shouts", chatPartner[p])) {
3199 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3200 chattingPartner = p; break;
3203 if(chattingPartner < 0)
3204 for(p=0; p<MAX_CHAT; p++) {
3205 if(!strcmp("shouts", chatPartner[p])) {
3206 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3207 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3208 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3209 chattingPartner = p; break;
3213 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3214 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3215 talker[0] = 0; Colorize(ColorTell, FALSE);
3216 chattingPartner = p; break;
3218 if(chattingPartner<0) i = oldi; else {
3219 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3220 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222 started = STARTED_COMMENT;
3223 parse_pos = 0; parse[0] = NULLCHAR;
3224 savingComment = 3 + chattingPartner; // counts as TRUE
3225 suppressKibitz = TRUE;
3228 } // [HGM] chat: end of patch
3231 if (appData.zippyTalk || appData.zippyPlay) {
3232 /* [DM] Backup address for color zippy lines */
3234 if (loggedOn == TRUE)
3235 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3236 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3238 } // [DM] 'else { ' deleted
3240 /* Regular tells and says */
3241 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3242 looking_at(buf, &i, "* (your partner) tells you: ") ||
3243 looking_at(buf, &i, "* says: ") ||
3244 /* Don't color "message" or "messages" output */
3245 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3246 looking_at(buf, &i, "*. * at *:*: ") ||
3247 looking_at(buf, &i, "--* (*:*): ") ||
3248 /* Message notifications (same color as tells) */
3249 looking_at(buf, &i, "* has left a message ") ||
3250 looking_at(buf, &i, "* just sent you a message:\n") ||
3251 /* Whispers and kibitzes */
3252 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3253 looking_at(buf, &i, "* kibitzes: ") ||
3255 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3257 if (tkind == 1 && strchr(star_match[0], ':')) {
3258 /* Avoid "tells you:" spoofs in channels */
3261 if (star_match[0][0] == NULLCHAR ||
3262 strchr(star_match[0], ' ') ||
3263 (tkind == 3 && strchr(star_match[1], ' '))) {
3264 /* Reject bogus matches */
3267 if (appData.colorize) {
3268 if (oldi > next_out) {
3269 SendToPlayer(&buf[next_out], oldi - next_out);
3274 Colorize(ColorTell, FALSE);
3275 curColor = ColorTell;
3278 Colorize(ColorKibitz, FALSE);
3279 curColor = ColorKibitz;
3282 p = strrchr(star_match[1], '(');
3289 Colorize(ColorChannel1, FALSE);
3290 curColor = ColorChannel1;
3292 Colorize(ColorChannel, FALSE);
3293 curColor = ColorChannel;
3297 curColor = ColorNormal;
3301 if (started == STARTED_NONE && appData.autoComment &&
3302 (gameMode == IcsObserving ||
3303 gameMode == IcsPlayingWhite ||
3304 gameMode == IcsPlayingBlack)) {
3305 parse_pos = i - oldi;
3306 memcpy(parse, &buf[oldi], parse_pos);
3307 parse[parse_pos] = NULLCHAR;
3308 started = STARTED_COMMENT;
3309 savingComment = TRUE;
3311 started = STARTED_CHATTER;
3312 savingComment = FALSE;
3319 if (looking_at(buf, &i, "* s-shouts: ") ||
3320 looking_at(buf, &i, "* c-shouts: ")) {
3321 if (appData.colorize) {
3322 if (oldi > next_out) {
3323 SendToPlayer(&buf[next_out], oldi - next_out);
3326 Colorize(ColorSShout, FALSE);
3327 curColor = ColorSShout;
3330 started = STARTED_CHATTER;
3334 if (looking_at(buf, &i, "--->")) {
3339 if (looking_at(buf, &i, "* shouts: ") ||
3340 looking_at(buf, &i, "--> ")) {
3341 if (appData.colorize) {
3342 if (oldi > next_out) {
3343 SendToPlayer(&buf[next_out], oldi - next_out);
3346 Colorize(ColorShout, FALSE);
3347 curColor = ColorShout;
3350 started = STARTED_CHATTER;
3354 if (looking_at( buf, &i, "Challenge:")) {
3355 if (appData.colorize) {
3356 if (oldi > next_out) {
3357 SendToPlayer(&buf[next_out], oldi - next_out);
3360 Colorize(ColorChallenge, FALSE);
3361 curColor = ColorChallenge;
3367 if (looking_at(buf, &i, "* offers you") ||
3368 looking_at(buf, &i, "* offers to be") ||
3369 looking_at(buf, &i, "* would like to") ||
3370 looking_at(buf, &i, "* requests to") ||
3371 looking_at(buf, &i, "Your opponent offers") ||
3372 looking_at(buf, &i, "Your opponent requests")) {
3374 if (appData.colorize) {
3375 if (oldi > next_out) {
3376 SendToPlayer(&buf[next_out], oldi - next_out);
3379 Colorize(ColorRequest, FALSE);
3380 curColor = ColorRequest;
3385 if (looking_at(buf, &i, "* (*) seeking")) {
3386 if (appData.colorize) {
3387 if (oldi > next_out) {
3388 SendToPlayer(&buf[next_out], oldi - next_out);
3391 Colorize(ColorSeek, FALSE);
3392 curColor = ColorSeek;
3397 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3399 if (looking_at(buf, &i, "\\ ")) {
3400 if (prevColor != ColorNormal) {
3401 if (oldi > next_out) {
3402 SendToPlayer(&buf[next_out], oldi - next_out);
3405 Colorize(prevColor, TRUE);
3406 curColor = prevColor;
3408 if (savingComment) {
3409 parse_pos = i - oldi;
3410 memcpy(parse, &buf[oldi], parse_pos);
3411 parse[parse_pos] = NULLCHAR;
3412 started = STARTED_COMMENT;
3413 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3414 chattingPartner = savingComment - 3; // kludge to remember the box
3416 started = STARTED_CHATTER;
3421 if (looking_at(buf, &i, "Black Strength :") ||
3422 looking_at(buf, &i, "<<< style 10 board >>>") ||
3423 looking_at(buf, &i, "<10>") ||
3424 looking_at(buf, &i, "#@#")) {
3425 /* Wrong board style */
3427 SendToICS(ics_prefix);
3428 SendToICS("set style 12\n");
3429 SendToICS(ics_prefix);
3430 SendToICS("refresh\n");
3434 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3436 have_sent_ICS_logon = 1;
3440 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3441 (looking_at(buf, &i, "\n<12> ") ||
3442 looking_at(buf, &i, "<12> "))) {
3444 if (oldi > next_out) {
3445 SendToPlayer(&buf[next_out], oldi - next_out);
3448 started = STARTED_BOARD;
3453 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3454 looking_at(buf, &i, "<b1> ")) {
3455 if (oldi > next_out) {
3456 SendToPlayer(&buf[next_out], oldi - next_out);
3459 started = STARTED_HOLDINGS;
3464 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3466 /* Header for a move list -- first line */
3468 switch (ics_getting_history) {
3472 case BeginningOfGame:
3473 /* User typed "moves" or "oldmoves" while we
3474 were idle. Pretend we asked for these
3475 moves and soak them up so user can step
3476 through them and/or save them.
3479 gameMode = IcsObserving;
3482 ics_getting_history = H_GOT_UNREQ_HEADER;
3484 case EditGame: /*?*/
3485 case EditPosition: /*?*/
3486 /* Should above feature work in these modes too? */
3487 /* For now it doesn't */
3488 ics_getting_history = H_GOT_UNWANTED_HEADER;
3491 ics_getting_history = H_GOT_UNWANTED_HEADER;
3496 /* Is this the right one? */
3497 if (gameInfo.white && gameInfo.black &&
3498 strcmp(gameInfo.white, star_match[0]) == 0 &&
3499 strcmp(gameInfo.black, star_match[2]) == 0) {
3501 ics_getting_history = H_GOT_REQ_HEADER;
3504 case H_GOT_REQ_HEADER:
3505 case H_GOT_UNREQ_HEADER:
3506 case H_GOT_UNWANTED_HEADER:
3507 case H_GETTING_MOVES:
3508 /* Should not happen */
3509 DisplayError(_("Error gathering move list: two headers"), 0);
3510 ics_getting_history = H_FALSE;
3514 /* Save player ratings into gameInfo if needed */
3515 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3516 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3517 (gameInfo.whiteRating == -1 ||
3518 gameInfo.blackRating == -1)) {
3520 gameInfo.whiteRating = string_to_rating(star_match[1]);
3521 gameInfo.blackRating = string_to_rating(star_match[3]);
3522 if (appData.debugMode)
3523 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3524 gameInfo.whiteRating, gameInfo.blackRating);
3529 if (looking_at(buf, &i,
3530 "* * match, initial time: * minute*, increment: * second")) {
3531 /* Header for a move list -- second line */
3532 /* Initial board will follow if this is a wild game */
3533 if (gameInfo.event != NULL) free(gameInfo.event);
3534 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3535 gameInfo.event = StrSave(str);
3536 /* [HGM] we switched variant. Translate boards if needed. */
3537 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3541 if (looking_at(buf, &i, "Move ")) {
3542 /* Beginning of a move list */
3543 switch (ics_getting_history) {
3545 /* Normally should not happen */
3546 /* Maybe user hit reset while we were parsing */
3549 /* Happens if we are ignoring a move list that is not
3550 * the one we just requested. Common if the user
3551 * tries to observe two games without turning off
3554 case H_GETTING_MOVES:
3555 /* Should not happen */
3556 DisplayError(_("Error gathering move list: nested"), 0);
3557 ics_getting_history = H_FALSE;
3559 case H_GOT_REQ_HEADER:
3560 ics_getting_history = H_GETTING_MOVES;
3561 started = STARTED_MOVES;
3563 if (oldi > next_out) {
3564 SendToPlayer(&buf[next_out], oldi - next_out);
3567 case H_GOT_UNREQ_HEADER:
3568 ics_getting_history = H_GETTING_MOVES;
3569 started = STARTED_MOVES_NOHIDE;
3572 case H_GOT_UNWANTED_HEADER:
3573 ics_getting_history = H_FALSE;
3579 if (looking_at(buf, &i, "% ") ||
3580 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3581 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3582 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3583 soughtPending = FALSE;
3587 if(suppressKibitz) next_out = i;
3588 savingComment = FALSE;
3592 case STARTED_MOVES_NOHIDE:
3593 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3594 parse[parse_pos + i - oldi] = NULLCHAR;
3595 ParseGameHistory(parse);
3597 if (appData.zippyPlay && first.initDone) {
3598 FeedMovesToProgram(&first, forwardMostMove);
3599 if (gameMode == IcsPlayingWhite) {
3600 if (WhiteOnMove(forwardMostMove)) {
3601 if (first.sendTime) {
3602 if (first.useColors) {
3603 SendToProgram("black\n", &first);
3605 SendTimeRemaining(&first, TRUE);
3607 if (first.useColors) {
3608 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3610 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3611 first.maybeThinking = TRUE;
3613 if (first.usePlayother) {
3614 if (first.sendTime) {
3615 SendTimeRemaining(&first, TRUE);
3617 SendToProgram("playother\n", &first);
3623 } else if (gameMode == IcsPlayingBlack) {
3624 if (!WhiteOnMove(forwardMostMove)) {
3625 if (first.sendTime) {
3626 if (first.useColors) {
3627 SendToProgram("white\n", &first);
3629 SendTimeRemaining(&first, FALSE);
3631 if (first.useColors) {
3632 SendToProgram("black\n", &first);
3634 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3635 first.maybeThinking = TRUE;
3637 if (first.usePlayother) {
3638 if (first.sendTime) {
3639 SendTimeRemaining(&first, FALSE);
3641 SendToProgram("playother\n", &first);
3650 if (gameMode == IcsObserving && ics_gamenum == -1) {
3651 /* Moves came from oldmoves or moves command
3652 while we weren't doing anything else.
3654 currentMove = forwardMostMove;
3655 ClearHighlights();/*!!could figure this out*/
3656 flipView = appData.flipView;
3657 DrawPosition(TRUE, boards[currentMove]);
3658 DisplayBothClocks();
3659 snprintf(str, MSG_SIZ, "%s %s %s",
3660 gameInfo.white, _("vs."), gameInfo.black);
3664 /* Moves were history of an active game */
3665 if (gameInfo.resultDetails != NULL) {
3666 free(gameInfo.resultDetails);
3667 gameInfo.resultDetails = NULL;
3670 HistorySet(parseList, backwardMostMove,
3671 forwardMostMove, currentMove-1);
3672 DisplayMove(currentMove - 1);
3673 if (started == STARTED_MOVES) next_out = i;
3674 started = STARTED_NONE;
3675 ics_getting_history = H_FALSE;
3678 case STARTED_OBSERVE:
3679 started = STARTED_NONE;
3680 SendToICS(ics_prefix);
3681 SendToICS("refresh\n");
3687 if(bookHit) { // [HGM] book: simulate book reply
3688 static char bookMove[MSG_SIZ]; // a bit generous?
3690 programStats.nodes = programStats.depth = programStats.time =
3691 programStats.score = programStats.got_only_move = 0;
3692 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3694 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3695 strcat(bookMove, bookHit);
3696 HandleMachineMove(bookMove, &first);
3701 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3702 started == STARTED_HOLDINGS ||
3703 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3704 /* Accumulate characters in move list or board */
3705 parse[parse_pos++] = buf[i];
3708 /* Start of game messages. Mostly we detect start of game
3709 when the first board image arrives. On some versions
3710 of the ICS, though, we need to do a "refresh" after starting
3711 to observe in order to get the current board right away. */
3712 if (looking_at(buf, &i, "Adding game * to observation list")) {
3713 started = STARTED_OBSERVE;
3717 /* Handle auto-observe */
3718 if (appData.autoObserve &&
3719 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3720 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3722 /* Choose the player that was highlighted, if any. */
3723 if (star_match[0][0] == '\033' ||
3724 star_match[1][0] != '\033') {
3725 player = star_match[0];
3727 player = star_match[2];
3729 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3730 ics_prefix, StripHighlightAndTitle(player));
3733 /* Save ratings from notify string */
3734 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3735 player1Rating = string_to_rating(star_match[1]);
3736 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3737 player2Rating = string_to_rating(star_match[3]);
3739 if (appData.debugMode)
3741 "Ratings from 'Game notification:' %s %d, %s %d\n",
3742 player1Name, player1Rating,
3743 player2Name, player2Rating);
3748 /* Deal with automatic examine mode after a game,
3749 and with IcsObserving -> IcsExamining transition */
3750 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3751 looking_at(buf, &i, "has made you an examiner of game *")) {
3753 int gamenum = atoi(star_match[0]);
3754 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3755 gamenum == ics_gamenum) {
3756 /* We were already playing or observing this game;
3757 no need to refetch history */
3758 gameMode = IcsExamining;
3760 pauseExamForwardMostMove = forwardMostMove;
3761 } else if (currentMove < forwardMostMove) {
3762 ForwardInner(forwardMostMove);
3765 /* I don't think this case really can happen */
3766 SendToICS(ics_prefix);
3767 SendToICS("refresh\n");
3772 /* Error messages */
3773 // if (ics_user_moved) {
3774 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3775 if (looking_at(buf, &i, "Illegal move") ||
3776 looking_at(buf, &i, "Not a legal move") ||
3777 looking_at(buf, &i, "Your king is in check") ||
3778 looking_at(buf, &i, "It isn't your turn") ||
3779 looking_at(buf, &i, "It is not your move")) {
3781 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3782 currentMove = forwardMostMove-1;
3783 DisplayMove(currentMove - 1); /* before DMError */
3784 DrawPosition(FALSE, boards[currentMove]);
3785 SwitchClocks(forwardMostMove-1); // [HGM] race
3786 DisplayBothClocks();
3788 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3794 if (looking_at(buf, &i, "still have time") ||
3795 looking_at(buf, &i, "not out of time") ||
3796 looking_at(buf, &i, "either player is out of time") ||
3797 looking_at(buf, &i, "has timeseal; checking")) {
3798 /* We must have called his flag a little too soon */
3799 whiteFlag = blackFlag = FALSE;
3803 if (looking_at(buf, &i, "added * seconds to") ||
3804 looking_at(buf, &i, "seconds were added to")) {
3805 /* Update the clocks */
3806 SendToICS(ics_prefix);
3807 SendToICS("refresh\n");
3811 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3812 ics_clock_paused = TRUE;
3817 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3818 ics_clock_paused = FALSE;
3823 /* Grab player ratings from the Creating: message.
3824 Note we have to check for the special case when
3825 the ICS inserts things like [white] or [black]. */
3826 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3827 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3829 0 player 1 name (not necessarily white)
3831 2 empty, white, or black (IGNORED)
3832 3 player 2 name (not necessarily black)
3835 The names/ratings are sorted out when the game
3836 actually starts (below).
3838 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3839 player1Rating = string_to_rating(star_match[1]);
3840 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3841 player2Rating = string_to_rating(star_match[4]);
3843 if (appData.debugMode)
3845 "Ratings from 'Creating:' %s %d, %s %d\n",
3846 player1Name, player1Rating,
3847 player2Name, player2Rating);
3852 /* Improved generic start/end-of-game messages */
3853 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3854 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3855 /* If tkind == 0: */
3856 /* star_match[0] is the game number */
3857 /* [1] is the white player's name */
3858 /* [2] is the black player's name */
3859 /* For end-of-game: */
3860 /* [3] is the reason for the game end */
3861 /* [4] is a PGN end game-token, preceded by " " */
3862 /* For start-of-game: */
3863 /* [3] begins with "Creating" or "Continuing" */
3864 /* [4] is " *" or empty (don't care). */
3865 int gamenum = atoi(star_match[0]);
3866 char *whitename, *blackname, *why, *endtoken;
3867 ChessMove endtype = EndOfFile;
3870 whitename = star_match[1];
3871 blackname = star_match[2];
3872 why = star_match[3];
3873 endtoken = star_match[4];
3875 whitename = star_match[1];
3876 blackname = star_match[3];
3877 why = star_match[5];
3878 endtoken = star_match[6];
3881 /* Game start messages */
3882 if (strncmp(why, "Creating ", 9) == 0 ||
3883 strncmp(why, "Continuing ", 11) == 0) {
3884 gs_gamenum = gamenum;
3885 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3886 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3887 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3889 if (appData.zippyPlay) {
3890 ZippyGameStart(whitename, blackname);
3893 partnerBoardValid = FALSE; // [HGM] bughouse
3897 /* Game end messages */
3898 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3899 ics_gamenum != gamenum) {
3902 while (endtoken[0] == ' ') endtoken++;
3903 switch (endtoken[0]) {
3906 endtype = GameUnfinished;
3909 endtype = BlackWins;
3912 if (endtoken[1] == '/')
3913 endtype = GameIsDrawn;
3915 endtype = WhiteWins;
3918 GameEnds(endtype, why, GE_ICS);
3920 if (appData.zippyPlay && first.initDone) {
3921 ZippyGameEnd(endtype, why);
3922 if (first.pr == NoProc) {
3923 /* Start the next process early so that we'll
3924 be ready for the next challenge */
3925 StartChessProgram(&first);
3927 /* Send "new" early, in case this command takes
3928 a long time to finish, so that we'll be ready
3929 for the next challenge. */
3930 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3934 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3938 if (looking_at(buf, &i, "Removing game * from observation") ||
3939 looking_at(buf, &i, "no longer observing game *") ||
3940 looking_at(buf, &i, "Game * (*) has no examiners")) {
3941 if (gameMode == IcsObserving &&
3942 atoi(star_match[0]) == ics_gamenum)
3944 /* icsEngineAnalyze */
3945 if (appData.icsEngineAnalyze) {
3952 ics_user_moved = FALSE;
3957 if (looking_at(buf, &i, "no longer examining game *")) {
3958 if (gameMode == IcsExamining &&
3959 atoi(star_match[0]) == ics_gamenum)
3963 ics_user_moved = FALSE;
3968 /* Advance leftover_start past any newlines we find,
3969 so only partial lines can get reparsed */
3970 if (looking_at(buf, &i, "\n")) {
3971 prevColor = curColor;
3972 if (curColor != ColorNormal) {
3973 if (oldi > next_out) {
3974 SendToPlayer(&buf[next_out], oldi - next_out);
3977 Colorize(ColorNormal, FALSE);
3978 curColor = ColorNormal;
3980 if (started == STARTED_BOARD) {
3981 started = STARTED_NONE;
3982 parse[parse_pos] = NULLCHAR;
3983 ParseBoard12(parse);
3986 /* Send premove here */
3987 if (appData.premove) {
3989 if (currentMove == 0 &&
3990 gameMode == IcsPlayingWhite &&
3991 appData.premoveWhite) {
3992 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3993 if (appData.debugMode)
3994 fprintf(debugFP, "Sending premove:\n");
3996 } else if (currentMove == 1 &&
3997 gameMode == IcsPlayingBlack &&
3998 appData.premoveBlack) {
3999 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4000 if (appData.debugMode)
4001 fprintf(debugFP, "Sending premove:\n");
4003 } else if (gotPremove) {
4005 ClearPremoveHighlights();
4006 if (appData.debugMode)
4007 fprintf(debugFP, "Sending premove:\n");
4008 UserMoveEvent(premoveFromX, premoveFromY,
4009 premoveToX, premoveToY,
4014 /* Usually suppress following prompt */
4015 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4016 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4017 if (looking_at(buf, &i, "*% ")) {
4018 savingComment = FALSE;
4023 } else if (started == STARTED_HOLDINGS) {
4025 char new_piece[MSG_SIZ];
4026 started = STARTED_NONE;
4027 parse[parse_pos] = NULLCHAR;
4028 if (appData.debugMode)
4029 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4030 parse, currentMove);
4031 if (sscanf(parse, " game %d", &gamenum) == 1) {
4032 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4033 if (gameInfo.variant == VariantNormal) {
4034 /* [HGM] We seem to switch variant during a game!
4035 * Presumably no holdings were displayed, so we have
4036 * to move the position two files to the right to
4037 * create room for them!
4039 VariantClass newVariant;
4040 switch(gameInfo.boardWidth) { // base guess on board width
4041 case 9: newVariant = VariantShogi; break;
4042 case 10: newVariant = VariantGreat; break;
4043 default: newVariant = VariantCrazyhouse; break;
4045 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4046 /* Get a move list just to see the header, which
4047 will tell us whether this is really bug or zh */
4048 if (ics_getting_history == H_FALSE) {
4049 ics_getting_history = H_REQUESTED;
4050 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4054 new_piece[0] = NULLCHAR;
4055 sscanf(parse, "game %d white [%s black [%s <- %s",
4056 &gamenum, white_holding, black_holding,
4058 white_holding[strlen(white_holding)-1] = NULLCHAR;
4059 black_holding[strlen(black_holding)-1] = NULLCHAR;
4060 /* [HGM] copy holdings to board holdings area */
4061 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4062 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4063 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4065 if (appData.zippyPlay && first.initDone) {
4066 ZippyHoldings(white_holding, black_holding,
4070 if (tinyLayout || smallLayout) {
4071 char wh[16], bh[16];
4072 PackHolding(wh, white_holding);
4073 PackHolding(bh, black_holding);
4074 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4075 gameInfo.white, gameInfo.black);
4077 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4078 gameInfo.white, white_holding, _("vs."),
4079 gameInfo.black, black_holding);
4081 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4082 DrawPosition(FALSE, boards[currentMove]);
4084 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4085 sscanf(parse, "game %d white [%s black [%s <- %s",
4086 &gamenum, white_holding, black_holding,
4088 white_holding[strlen(white_holding)-1] = NULLCHAR;
4089 black_holding[strlen(black_holding)-1] = NULLCHAR;
4090 /* [HGM] copy holdings to partner-board holdings area */
4091 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4092 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4093 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4094 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4095 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4098 /* Suppress following prompt */
4099 if (looking_at(buf, &i, "*% ")) {
4100 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4101 savingComment = FALSE;
4109 i++; /* skip unparsed character and loop back */
4112 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4113 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4114 // SendToPlayer(&buf[next_out], i - next_out);
4115 started != STARTED_HOLDINGS && leftover_start > next_out) {
4116 SendToPlayer(&buf[next_out], leftover_start - next_out);
4120 leftover_len = buf_len - leftover_start;
4121 /* if buffer ends with something we couldn't parse,
4122 reparse it after appending the next read */
4124 } else if (count == 0) {
4125 RemoveInputSource(isr);
4126 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4128 DisplayFatalError(_("Error reading from ICS"), error, 1);
4133 /* Board style 12 looks like this:
4135 <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
4137 * The "<12> " is stripped before it gets to this routine. The two
4138 * trailing 0's (flip state and clock ticking) are later addition, and
4139 * some chess servers may not have them, or may have only the first.
4140 * Additional trailing fields may be added in the future.
4143 #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"
4145 #define RELATION_OBSERVING_PLAYED 0
4146 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4147 #define RELATION_PLAYING_MYMOVE 1
4148 #define RELATION_PLAYING_NOTMYMOVE -1
4149 #define RELATION_EXAMINING 2
4150 #define RELATION_ISOLATED_BOARD -3
4151 #define RELATION_STARTING_POSITION -4 /* FICS only */
4154 ParseBoard12 (char *string)
4156 GameMode newGameMode;
4157 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4158 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4159 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4160 char to_play, board_chars[200];
4161 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4162 char black[32], white[32];
4164 int prevMove = currentMove;
4167 int fromX, fromY, toX, toY;
4169 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4170 char *bookHit = NULL; // [HGM] book
4171 Boolean weird = FALSE, reqFlag = FALSE;
4173 fromX = fromY = toX = toY = -1;
4177 if (appData.debugMode)
4178 fprintf(debugFP, _("Parsing board: %s\n"), string);
4180 move_str[0] = NULLCHAR;
4181 elapsed_time[0] = NULLCHAR;
4182 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4184 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4185 if(string[i] == ' ') { ranks++; files = 0; }
4187 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4190 for(j = 0; j <i; j++) board_chars[j] = string[j];
4191 board_chars[i] = '\0';
4194 n = sscanf(string, PATTERN, &to_play, &double_push,
4195 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4196 &gamenum, white, black, &relation, &basetime, &increment,
4197 &white_stren, &black_stren, &white_time, &black_time,
4198 &moveNum, str, elapsed_time, move_str, &ics_flip,
4202 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4203 DisplayError(str, 0);
4207 /* Convert the move number to internal form */
4208 moveNum = (moveNum - 1) * 2;
4209 if (to_play == 'B') moveNum++;
4210 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4211 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4217 case RELATION_OBSERVING_PLAYED:
4218 case RELATION_OBSERVING_STATIC:
4219 if (gamenum == -1) {
4220 /* Old ICC buglet */
4221 relation = RELATION_OBSERVING_STATIC;
4223 newGameMode = IcsObserving;
4225 case RELATION_PLAYING_MYMOVE:
4226 case RELATION_PLAYING_NOTMYMOVE:
4228 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4229 IcsPlayingWhite : IcsPlayingBlack;
4230 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4232 case RELATION_EXAMINING:
4233 newGameMode = IcsExamining;
4235 case RELATION_ISOLATED_BOARD:
4237 /* Just display this board. If user was doing something else,
4238 we will forget about it until the next board comes. */
4239 newGameMode = IcsIdle;
4241 case RELATION_STARTING_POSITION:
4242 newGameMode = gameMode;
4246 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4247 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4248 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4249 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4250 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4251 static int lastBgGame = -1;
4253 for (k = 0; k < ranks; k++) {
4254 for (j = 0; j < files; j++)
4255 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4256 if(gameInfo.holdingsWidth > 1) {
4257 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4258 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4261 CopyBoard(partnerBoard, board);
4262 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4263 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4264 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4265 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4266 if(toSqr = strchr(str, '-')) {
4267 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4268 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4269 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4270 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4271 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4272 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4274 DisplayWhiteClock(white_time*fac, to_play == 'W');
4275 DisplayBlackClock(black_time*fac, to_play != 'W');
4276 activePartner = to_play;
4277 if(gamenum != lastBgGame) {
4279 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4282 lastBgGame = gamenum;
4283 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4284 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4285 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4286 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4287 DisplayMessage(partnerStatus, "");
4288 partnerBoardValid = TRUE;
4292 if(appData.dualBoard && appData.bgObserve) {
4293 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4294 SendToICS(ics_prefix), SendToICS("pobserve\n");
4295 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4297 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4302 /* Modify behavior for initial board display on move listing
4305 switch (ics_getting_history) {
4309 case H_GOT_REQ_HEADER:
4310 case H_GOT_UNREQ_HEADER:
4311 /* This is the initial position of the current game */
4312 gamenum = ics_gamenum;
4313 moveNum = 0; /* old ICS bug workaround */
4314 if (to_play == 'B') {
4315 startedFromSetupPosition = TRUE;
4316 blackPlaysFirst = TRUE;
4318 if (forwardMostMove == 0) forwardMostMove = 1;
4319 if (backwardMostMove == 0) backwardMostMove = 1;
4320 if (currentMove == 0) currentMove = 1;
4322 newGameMode = gameMode;
4323 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4325 case H_GOT_UNWANTED_HEADER:
4326 /* This is an initial board that we don't want */
4328 case H_GETTING_MOVES:
4329 /* Should not happen */
4330 DisplayError(_("Error gathering move list: extra board"), 0);
4331 ics_getting_history = H_FALSE;
4335 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4336 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4337 weird && (int)gameInfo.variant < (int)VariantShogi) {
4338 /* [HGM] We seem to have switched variant unexpectedly
4339 * Try to guess new variant from board size
4341 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4342 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4343 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4344 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4345 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4346 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4347 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4348 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4349 /* Get a move list just to see the header, which
4350 will tell us whether this is really bug or zh */
4351 if (ics_getting_history == H_FALSE) {
4352 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4353 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4358 /* Take action if this is the first board of a new game, or of a
4359 different game than is currently being displayed. */
4360 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4361 relation == RELATION_ISOLATED_BOARD) {
4363 /* Forget the old game and get the history (if any) of the new one */
4364 if (gameMode != BeginningOfGame) {
4368 if (appData.autoRaiseBoard) BoardToTop();
4370 if (gamenum == -1) {
4371 newGameMode = IcsIdle;
4372 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4373 appData.getMoveList && !reqFlag) {
4374 /* Need to get game history */
4375 ics_getting_history = H_REQUESTED;
4376 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4380 /* Initially flip the board to have black on the bottom if playing
4381 black or if the ICS flip flag is set, but let the user change
4382 it with the Flip View button. */
4383 flipView = appData.autoFlipView ?
4384 (newGameMode == IcsPlayingBlack) || ics_flip :
4387 /* Done with values from previous mode; copy in new ones */
4388 gameMode = newGameMode;
4390 ics_gamenum = gamenum;
4391 if (gamenum == gs_gamenum) {
4392 int klen = strlen(gs_kind);
4393 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4394 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4395 gameInfo.event = StrSave(str);
4397 gameInfo.event = StrSave("ICS game");
4399 gameInfo.site = StrSave(appData.icsHost);
4400 gameInfo.date = PGNDate();
4401 gameInfo.round = StrSave("-");
4402 gameInfo.white = StrSave(white);
4403 gameInfo.black = StrSave(black);
4404 timeControl = basetime * 60 * 1000;
4406 timeIncrement = increment * 1000;
4407 movesPerSession = 0;
4408 gameInfo.timeControl = TimeControlTagValue();
4409 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4410 if (appData.debugMode) {
4411 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4412 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4413 setbuf(debugFP, NULL);
4416 gameInfo.outOfBook = NULL;
4418 /* Do we have the ratings? */
4419 if (strcmp(player1Name, white) == 0 &&
4420 strcmp(player2Name, black) == 0) {
4421 if (appData.debugMode)
4422 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4423 player1Rating, player2Rating);
4424 gameInfo.whiteRating = player1Rating;
4425 gameInfo.blackRating = player2Rating;
4426 } else if (strcmp(player2Name, white) == 0 &&
4427 strcmp(player1Name, black) == 0) {
4428 if (appData.debugMode)
4429 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4430 player2Rating, player1Rating);
4431 gameInfo.whiteRating = player2Rating;
4432 gameInfo.blackRating = player1Rating;
4434 player1Name[0] = player2Name[0] = NULLCHAR;
4436 /* Silence shouts if requested */
4437 if (appData.quietPlay &&
4438 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4439 SendToICS(ics_prefix);
4440 SendToICS("set shout 0\n");
4444 /* Deal with midgame name changes */
4446 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4447 if (gameInfo.white) free(gameInfo.white);
4448 gameInfo.white = StrSave(white);
4450 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4451 if (gameInfo.black) free(gameInfo.black);
4452 gameInfo.black = StrSave(black);
4456 /* Throw away game result if anything actually changes in examine mode */
4457 if (gameMode == IcsExamining && !newGame) {
4458 gameInfo.result = GameUnfinished;
4459 if (gameInfo.resultDetails != NULL) {
4460 free(gameInfo.resultDetails);
4461 gameInfo.resultDetails = NULL;
4465 /* In pausing && IcsExamining mode, we ignore boards coming
4466 in if they are in a different variation than we are. */
4467 if (pauseExamInvalid) return;
4468 if (pausing && gameMode == IcsExamining) {
4469 if (moveNum <= pauseExamForwardMostMove) {
4470 pauseExamInvalid = TRUE;
4471 forwardMostMove = pauseExamForwardMostMove;
4476 if (appData.debugMode) {
4477 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4479 /* Parse the board */
4480 for (k = 0; k < ranks; k++) {
4481 for (j = 0; j < files; j++)
4482 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4483 if(gameInfo.holdingsWidth > 1) {
4484 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4485 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4488 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4489 board[5][BOARD_RGHT+1] = WhiteAngel;
4490 board[6][BOARD_RGHT+1] = WhiteMarshall;
4491 board[1][0] = BlackMarshall;
4492 board[2][0] = BlackAngel;
4493 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4495 CopyBoard(boards[moveNum], board);
4496 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4498 startedFromSetupPosition =
4499 !CompareBoards(board, initialPosition);
4500 if(startedFromSetupPosition)
4501 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4504 /* [HGM] Set castling rights. Take the outermost Rooks,
4505 to make it also work for FRC opening positions. Note that board12
4506 is really defective for later FRC positions, as it has no way to
4507 indicate which Rook can castle if they are on the same side of King.
4508 For the initial position we grant rights to the outermost Rooks,
4509 and remember thos rights, and we then copy them on positions
4510 later in an FRC game. This means WB might not recognize castlings with
4511 Rooks that have moved back to their original position as illegal,
4512 but in ICS mode that is not its job anyway.
4514 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4515 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4517 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4518 if(board[0][i] == WhiteRook) j = i;
4519 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4520 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4521 if(board[0][i] == WhiteRook) j = i;
4522 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4523 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4524 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4525 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4526 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4527 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4528 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4530 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4531 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4532 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4534 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4535 if(board[BOARD_HEIGHT-1][k] == bKing)
4536 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4537 if(gameInfo.variant == VariantTwoKings) {
4538 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4539 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4540 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4543 r = boards[moveNum][CASTLING][0] = initialRights[0];
4544 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4545 r = boards[moveNum][CASTLING][1] = initialRights[1];
4546 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4547 r = boards[moveNum][CASTLING][3] = initialRights[3];
4548 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4549 r = boards[moveNum][CASTLING][4] = initialRights[4];
4550 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4551 /* wildcastle kludge: always assume King has rights */
4552 r = boards[moveNum][CASTLING][2] = initialRights[2];
4553 r = boards[moveNum][CASTLING][5] = initialRights[5];
4555 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4556 boards[moveNum][EP_STATUS] = EP_NONE;
4557 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4558 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4559 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4562 if (ics_getting_history == H_GOT_REQ_HEADER ||
4563 ics_getting_history == H_GOT_UNREQ_HEADER) {
4564 /* This was an initial position from a move list, not
4565 the current position */
4569 /* Update currentMove and known move number limits */
4570 newMove = newGame || moveNum > forwardMostMove;
4573 forwardMostMove = backwardMostMove = currentMove = moveNum;
4574 if (gameMode == IcsExamining && moveNum == 0) {
4575 /* Workaround for ICS limitation: we are not told the wild
4576 type when starting to examine a game. But if we ask for
4577 the move list, the move list header will tell us */
4578 ics_getting_history = H_REQUESTED;
4579 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4582 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4583 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4585 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4586 /* [HGM] applied this also to an engine that is silently watching */
4587 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4588 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4589 gameInfo.variant == currentlyInitializedVariant) {
4590 takeback = forwardMostMove - moveNum;
4591 for (i = 0; i < takeback; i++) {
4592 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4593 SendToProgram("undo\n", &first);
4598 forwardMostMove = moveNum;
4599 if (!pausing || currentMove > forwardMostMove)
4600 currentMove = forwardMostMove;
4602 /* New part of history that is not contiguous with old part */
4603 if (pausing && gameMode == IcsExamining) {
4604 pauseExamInvalid = TRUE;
4605 forwardMostMove = pauseExamForwardMostMove;
4608 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4610 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4611 // [HGM] when we will receive the move list we now request, it will be
4612 // fed to the engine from the first move on. So if the engine is not
4613 // in the initial position now, bring it there.
4614 InitChessProgram(&first, 0);
4617 ics_getting_history = H_REQUESTED;
4618 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4621 forwardMostMove = backwardMostMove = currentMove = moveNum;
4624 /* Update the clocks */
4625 if (strchr(elapsed_time, '.')) {
4627 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4628 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4630 /* Time is in seconds */
4631 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4632 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4637 if (appData.zippyPlay && newGame &&
4638 gameMode != IcsObserving && gameMode != IcsIdle &&
4639 gameMode != IcsExamining)
4640 ZippyFirstBoard(moveNum, basetime, increment);
4643 /* Put the move on the move list, first converting
4644 to canonical algebraic form. */
4646 if (appData.debugMode) {
4647 if (appData.debugMode) { int f = forwardMostMove;
4648 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4649 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4650 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4652 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4653 fprintf(debugFP, "moveNum = %d\n", moveNum);
4654 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4655 setbuf(debugFP, NULL);
4657 if (moveNum <= backwardMostMove) {
4658 /* We don't know what the board looked like before
4660 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4661 strcat(parseList[moveNum - 1], " ");
4662 strcat(parseList[moveNum - 1], elapsed_time);
4663 moveList[moveNum - 1][0] = NULLCHAR;
4664 } else if (strcmp(move_str, "none") == 0) {
4665 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4666 /* Again, we don't know what the board looked like;
4667 this is really the start of the game. */
4668 parseList[moveNum - 1][0] = NULLCHAR;
4669 moveList[moveNum - 1][0] = NULLCHAR;
4670 backwardMostMove = moveNum;
4671 startedFromSetupPosition = TRUE;
4672 fromX = fromY = toX = toY = -1;
4674 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4675 // So we parse the long-algebraic move string in stead of the SAN move
4676 int valid; char buf[MSG_SIZ], *prom;
4678 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4679 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4680 // str looks something like "Q/a1-a2"; kill the slash
4682 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4683 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4684 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4685 strcat(buf, prom); // long move lacks promo specification!
4686 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4687 if(appData.debugMode)
4688 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4689 safeStrCpy(move_str, buf, MSG_SIZ);
4691 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4692 &fromX, &fromY, &toX, &toY, &promoChar)
4693 || ParseOneMove(buf, moveNum - 1, &moveType,
4694 &fromX, &fromY, &toX, &toY, &promoChar);
4695 // end of long SAN patch
4697 (void) CoordsToAlgebraic(boards[moveNum - 1],
4698 PosFlags(moveNum - 1),
4699 fromY, fromX, toY, toX, promoChar,
4700 parseList[moveNum-1]);
4701 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4707 if(gameInfo.variant != VariantShogi)
4708 strcat(parseList[moveNum - 1], "+");
4711 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4712 strcat(parseList[moveNum - 1], "#");
4715 strcat(parseList[moveNum - 1], " ");
4716 strcat(parseList[moveNum - 1], elapsed_time);
4717 /* currentMoveString is set as a side-effect of ParseOneMove */
4718 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4719 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4720 strcat(moveList[moveNum - 1], "\n");
4722 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4723 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4724 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4725 ChessSquare old, new = boards[moveNum][k][j];
4726 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4727 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4728 if(old == new) continue;
4729 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4730 else if(new == WhiteWazir || new == BlackWazir) {
4731 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4732 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4733 else boards[moveNum][k][j] = old; // preserve type of Gold
4734 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4735 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4738 /* Move from ICS was illegal!? Punt. */
4739 if (appData.debugMode) {
4740 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4741 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4743 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4744 strcat(parseList[moveNum - 1], " ");
4745 strcat(parseList[moveNum - 1], elapsed_time);
4746 moveList[moveNum - 1][0] = NULLCHAR;
4747 fromX = fromY = toX = toY = -1;
4750 if (appData.debugMode) {
4751 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4752 setbuf(debugFP, NULL);
4756 /* Send move to chess program (BEFORE animating it). */
4757 if (appData.zippyPlay && !newGame && newMove &&
4758 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4760 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4761 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4762 if (moveList[moveNum - 1][0] == NULLCHAR) {
4763 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4765 DisplayError(str, 0);
4767 if (first.sendTime) {
4768 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4770 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4771 if (firstMove && !bookHit) {
4773 if (first.useColors) {
4774 SendToProgram(gameMode == IcsPlayingWhite ?
4776 "black\ngo\n", &first);
4778 SendToProgram("go\n", &first);
4780 first.maybeThinking = TRUE;
4783 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4784 if (moveList[moveNum - 1][0] == NULLCHAR) {
4785 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4786 DisplayError(str, 0);
4788 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4789 SendMoveToProgram(moveNum - 1, &first);
4796 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4797 /* If move comes from a remote source, animate it. If it
4798 isn't remote, it will have already been animated. */
4799 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4800 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4802 if (!pausing && appData.highlightLastMove) {
4803 SetHighlights(fromX, fromY, toX, toY);
4807 /* Start the clocks */
4808 whiteFlag = blackFlag = FALSE;
4809 appData.clockMode = !(basetime == 0 && increment == 0);
4811 ics_clock_paused = TRUE;
4813 } else if (ticking == 1) {
4814 ics_clock_paused = FALSE;
4816 if (gameMode == IcsIdle ||
4817 relation == RELATION_OBSERVING_STATIC ||
4818 relation == RELATION_EXAMINING ||
4820 DisplayBothClocks();
4824 /* Display opponents and material strengths */
4825 if (gameInfo.variant != VariantBughouse &&
4826 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4827 if (tinyLayout || smallLayout) {
4828 if(gameInfo.variant == VariantNormal)
4829 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4830 gameInfo.white, white_stren, gameInfo.black, black_stren,
4831 basetime, increment);
4833 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4834 gameInfo.white, white_stren, gameInfo.black, black_stren,
4835 basetime, increment, (int) gameInfo.variant);
4837 if(gameInfo.variant == VariantNormal)
4838 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4839 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4840 basetime, increment);
4842 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4843 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4844 basetime, increment, VariantName(gameInfo.variant));
4847 if (appData.debugMode) {
4848 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4853 /* Display the board */
4854 if (!pausing && !appData.noGUI) {
4856 if (appData.premove)
4858 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4859 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4860 ClearPremoveHighlights();
4862 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4863 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4864 DrawPosition(j, boards[currentMove]);
4866 DisplayMove(moveNum - 1);
4867 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4868 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4869 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4870 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4874 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4876 if(bookHit) { // [HGM] book: simulate book reply
4877 static char bookMove[MSG_SIZ]; // a bit generous?
4879 programStats.nodes = programStats.depth = programStats.time =
4880 programStats.score = programStats.got_only_move = 0;
4881 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4883 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4884 strcat(bookMove, bookHit);
4885 HandleMachineMove(bookMove, &first);
4894 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4895 ics_getting_history = H_REQUESTED;
4896 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4902 SendToBoth (char *msg)
4903 { // to make it easy to keep two engines in step in dual analysis
4904 SendToProgram(msg, &first);
4905 if(second.analyzing) SendToProgram(msg, &second);
4909 AnalysisPeriodicEvent (int force)
4911 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4912 && !force) || !appData.periodicUpdates)
4915 /* Send . command to Crafty to collect stats */
4918 /* Don't send another until we get a response (this makes
4919 us stop sending to old Crafty's which don't understand
4920 the "." command (sending illegal cmds resets node count & time,
4921 which looks bad)) */
4922 programStats.ok_to_send = 0;
4926 ics_update_width (int new_width)
4928 ics_printf("set width %d\n", new_width);
4932 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4936 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4937 // null move in variant where engine does not understand it (for analysis purposes)
4938 SendBoard(cps, moveNum + 1); // send position after move in stead.
4941 if (cps->useUsermove) {
4942 SendToProgram("usermove ", cps);
4946 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4947 int len = space - parseList[moveNum];
4948 memcpy(buf, parseList[moveNum], len);
4950 buf[len] = NULLCHAR;
4952 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4954 SendToProgram(buf, cps);
4956 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4957 AlphaRank(moveList[moveNum], 4);
4958 SendToProgram(moveList[moveNum], cps);
4959 AlphaRank(moveList[moveNum], 4); // and back
4961 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4962 * the engine. It would be nice to have a better way to identify castle
4964 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4965 && cps->useOOCastle) {
4966 int fromX = moveList[moveNum][0] - AAA;
4967 int fromY = moveList[moveNum][1] - ONE;
4968 int toX = moveList[moveNum][2] - AAA;
4969 int toY = moveList[moveNum][3] - ONE;
4970 if((boards[moveNum][fromY][fromX] == WhiteKing
4971 && boards[moveNum][toY][toX] == WhiteRook)
4972 || (boards[moveNum][fromY][fromX] == BlackKing
4973 && boards[moveNum][toY][toX] == BlackRook)) {
4974 if(toX > fromX) SendToProgram("O-O\n", cps);
4975 else SendToProgram("O-O-O\n", cps);
4977 else SendToProgram(moveList[moveNum], cps);
4979 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4980 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4981 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4982 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4983 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4985 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4986 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4987 SendToProgram(buf, cps);
4989 else SendToProgram(moveList[moveNum], cps);
4990 /* End of additions by Tord */
4993 /* [HGM] setting up the opening has brought engine in force mode! */
4994 /* Send 'go' if we are in a mode where machine should play. */
4995 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4996 (gameMode == TwoMachinesPlay ||
4998 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5000 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5001 SendToProgram("go\n", cps);
5002 if (appData.debugMode) {
5003 fprintf(debugFP, "(extra)\n");
5006 setboardSpoiledMachineBlack = 0;
5010 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5012 char user_move[MSG_SIZ];
5015 if(gameInfo.variant == VariantSChess && promoChar) {
5016 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5017 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5018 } else suffix[0] = NULLCHAR;
5022 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5023 (int)moveType, fromX, fromY, toX, toY);
5024 DisplayError(user_move + strlen("say "), 0);
5026 case WhiteKingSideCastle:
5027 case BlackKingSideCastle:
5028 case WhiteQueenSideCastleWild:
5029 case BlackQueenSideCastleWild:
5031 case WhiteHSideCastleFR:
5032 case BlackHSideCastleFR:
5034 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5036 case WhiteQueenSideCastle:
5037 case BlackQueenSideCastle:
5038 case WhiteKingSideCastleWild:
5039 case BlackKingSideCastleWild:
5041 case WhiteASideCastleFR:
5042 case BlackASideCastleFR:
5044 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5046 case WhiteNonPromotion:
5047 case BlackNonPromotion:
5048 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5050 case WhitePromotion:
5051 case BlackPromotion:
5052 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5053 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5054 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5055 PieceToChar(WhiteFerz));
5056 else if(gameInfo.variant == VariantGreat)
5057 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5058 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5059 PieceToChar(WhiteMan));
5061 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5062 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5068 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5069 ToUpper(PieceToChar((ChessSquare) fromX)),
5070 AAA + toX, ONE + toY);
5072 case IllegalMove: /* could be a variant we don't quite understand */
5073 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5075 case WhiteCapturesEnPassant:
5076 case BlackCapturesEnPassant:
5077 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5078 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5081 SendToICS(user_move);
5082 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5083 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5088 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5089 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5090 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5091 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5092 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5095 if(gameMode != IcsExamining) { // is this ever not the case?
5096 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5098 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5099 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5100 } else { // on FICS we must first go to general examine mode
5101 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5103 if(gameInfo.variant != VariantNormal) {
5104 // try figure out wild number, as xboard names are not always valid on ICS
5105 for(i=1; i<=36; i++) {
5106 snprintf(buf, MSG_SIZ, "wild/%d", i);
5107 if(StringToVariant(buf) == gameInfo.variant) break;
5109 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5110 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5111 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5112 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5113 SendToICS(ics_prefix);
5115 if(startedFromSetupPosition || backwardMostMove != 0) {
5116 fen = PositionToFEN(backwardMostMove, NULL);
5117 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5118 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5120 } else { // FICS: everything has to set by separate bsetup commands
5121 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5122 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5124 if(!WhiteOnMove(backwardMostMove)) {
5125 SendToICS("bsetup tomove black\n");
5127 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5128 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5130 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5131 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5133 i = boards[backwardMostMove][EP_STATUS];
5134 if(i >= 0) { // set e.p.
5135 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5141 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5142 SendToICS("bsetup done\n"); // switch to normal examining.
5144 for(i = backwardMostMove; i<last; i++) {
5146 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5147 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5148 int len = strlen(moveList[i]);
5149 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5150 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5154 SendToICS(ics_prefix);
5155 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5159 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5161 if (rf == DROP_RANK) {
5162 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5163 sprintf(move, "%c@%c%c\n",
5164 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5166 if (promoChar == 'x' || promoChar == NULLCHAR) {
5167 sprintf(move, "%c%c%c%c\n",
5168 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5170 sprintf(move, "%c%c%c%c%c\n",
5171 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5177 ProcessICSInitScript (FILE *f)
5181 while (fgets(buf, MSG_SIZ, f)) {
5182 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5189 static int lastX, lastY, selectFlag, dragging;
5194 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5195 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5196 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5197 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5198 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5199 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5202 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5203 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5204 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5205 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5206 if(!step) step = -1;
5207 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5208 appData.testLegality && (promoSweep == king ||
5209 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5211 int victim = boards[currentMove][toY][toX];
5212 boards[currentMove][toY][toX] = promoSweep;
5213 DrawPosition(FALSE, boards[currentMove]);
5214 boards[currentMove][toY][toX] = victim;
5216 ChangeDragPiece(promoSweep);
5220 PromoScroll (int x, int y)
5224 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5225 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5226 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5227 if(!step) return FALSE;
5228 lastX = x; lastY = y;
5229 if((promoSweep < BlackPawn) == flipView) step = -step;
5230 if(step > 0) selectFlag = 1;
5231 if(!selectFlag) Sweep(step);
5236 NextPiece (int step)
5238 ChessSquare piece = boards[currentMove][toY][toX];
5241 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5242 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5243 if(!step) step = -1;
5244 } while(PieceToChar(pieceSweep) == '.');
5245 boards[currentMove][toY][toX] = pieceSweep;
5246 DrawPosition(FALSE, boards[currentMove]);
5247 boards[currentMove][toY][toX] = piece;
5249 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5251 AlphaRank (char *move, int n)
5253 // char *p = move, c; int x, y;
5255 if (appData.debugMode) {
5256 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5260 move[2]>='0' && move[2]<='9' &&
5261 move[3]>='a' && move[3]<='x' ) {
5263 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5264 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5266 if(move[0]>='0' && move[0]<='9' &&
5267 move[1]>='a' && move[1]<='x' &&
5268 move[2]>='0' && move[2]<='9' &&
5269 move[3]>='a' && move[3]<='x' ) {
5270 /* input move, Shogi -> normal */
5271 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5272 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5273 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5274 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5277 move[3]>='0' && move[3]<='9' &&
5278 move[2]>='a' && move[2]<='x' ) {
5280 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5281 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5284 move[0]>='a' && move[0]<='x' &&
5285 move[3]>='0' && move[3]<='9' &&
5286 move[2]>='a' && move[2]<='x' ) {
5287 /* output move, normal -> Shogi */
5288 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5289 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5290 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5291 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5292 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5294 if (appData.debugMode) {
5295 fprintf(debugFP, " out = '%s'\n", move);
5299 char yy_textstr[8000];
5301 /* Parser for moves from gnuchess, ICS, or user typein box */
5303 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5305 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5307 switch (*moveType) {
5308 case WhitePromotion:
5309 case BlackPromotion:
5310 case WhiteNonPromotion:
5311 case BlackNonPromotion:
5313 case WhiteCapturesEnPassant:
5314 case BlackCapturesEnPassant:
5315 case WhiteKingSideCastle:
5316 case WhiteQueenSideCastle:
5317 case BlackKingSideCastle:
5318 case BlackQueenSideCastle:
5319 case WhiteKingSideCastleWild:
5320 case WhiteQueenSideCastleWild:
5321 case BlackKingSideCastleWild:
5322 case BlackQueenSideCastleWild:
5323 /* Code added by Tord: */
5324 case WhiteHSideCastleFR:
5325 case WhiteASideCastleFR:
5326 case BlackHSideCastleFR:
5327 case BlackASideCastleFR:
5328 /* End of code added by Tord */
5329 case IllegalMove: /* bug or odd chess variant */
5330 *fromX = currentMoveString[0] - AAA;
5331 *fromY = currentMoveString[1] - ONE;
5332 *toX = currentMoveString[2] - AAA;
5333 *toY = currentMoveString[3] - ONE;
5334 *promoChar = currentMoveString[4];
5335 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5336 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5337 if (appData.debugMode) {
5338 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5340 *fromX = *fromY = *toX = *toY = 0;
5343 if (appData.testLegality) {
5344 return (*moveType != IllegalMove);
5346 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5347 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5352 *fromX = *moveType == WhiteDrop ?
5353 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5354 (int) CharToPiece(ToLower(currentMoveString[0]));
5356 *toX = currentMoveString[2] - AAA;
5357 *toY = currentMoveString[3] - ONE;
5358 *promoChar = NULLCHAR;
5362 case ImpossibleMove:
5372 if (appData.debugMode) {
5373 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5376 *fromX = *fromY = *toX = *toY = 0;
5377 *promoChar = NULLCHAR;
5382 Boolean pushed = FALSE;
5383 char *lastParseAttempt;
5386 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5387 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5388 int fromX, fromY, toX, toY; char promoChar;
5393 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5394 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5397 endPV = forwardMostMove;
5399 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5400 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5401 lastParseAttempt = pv;
5402 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5403 if(!valid && nr == 0 &&
5404 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5405 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5406 // Hande case where played move is different from leading PV move
5407 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5408 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5409 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5410 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5411 endPV += 2; // if position different, keep this
5412 moveList[endPV-1][0] = fromX + AAA;
5413 moveList[endPV-1][1] = fromY + ONE;
5414 moveList[endPV-1][2] = toX + AAA;
5415 moveList[endPV-1][3] = toY + ONE;
5416 parseList[endPV-1][0] = NULLCHAR;
5417 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5420 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5421 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5422 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5423 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5424 valid++; // allow comments in PV
5428 if(endPV+1 > framePtr) break; // no space, truncate
5431 CopyBoard(boards[endPV], boards[endPV-1]);
5432 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5433 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5434 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5435 CoordsToAlgebraic(boards[endPV - 1],
5436 PosFlags(endPV - 1),
5437 fromY, fromX, toY, toX, promoChar,
5438 parseList[endPV - 1]);
5440 if(atEnd == 2) return; // used hidden, for PV conversion
5441 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5442 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5443 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5444 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5445 DrawPosition(TRUE, boards[currentMove]);
5449 MultiPV (ChessProgramState *cps)
5450 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5452 for(i=0; i<cps->nrOptions; i++)
5453 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5459 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5461 int startPV, multi, lineStart, origIndex = index;
5462 char *p, buf2[MSG_SIZ];
5463 ChessProgramState *cps = (pane ? &second : &first);
5465 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5466 lastX = x; lastY = y;
5467 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5468 lineStart = startPV = index;
5469 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5470 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5472 do{ while(buf[index] && buf[index] != '\n') index++;
5473 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5475 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5476 int n = cps->option[multi].value;
5477 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5478 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5479 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5480 cps->option[multi].value = n;
5483 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5484 ExcludeClick(origIndex - lineStart);
5487 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5488 *start = startPV; *end = index-1;
5495 static char buf[10*MSG_SIZ];
5496 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5498 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5499 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5500 for(i = forwardMostMove; i<endPV; i++){
5501 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5502 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5505 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5506 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5512 LoadPV (int x, int y)
5513 { // called on right mouse click to load PV
5514 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5515 lastX = x; lastY = y;
5516 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5523 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5524 if(endPV < 0) return;
5525 if(appData.autoCopyPV) CopyFENToClipboard();
5527 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5528 Boolean saveAnimate = appData.animate;
5530 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5531 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5532 } else storedGames--; // abandon shelved tail of original game
5535 forwardMostMove = currentMove;
5536 currentMove = oldFMM;
5537 appData.animate = FALSE;
5538 ToNrEvent(forwardMostMove);
5539 appData.animate = saveAnimate;
5541 currentMove = forwardMostMove;
5542 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5543 ClearPremoveHighlights();
5544 DrawPosition(TRUE, boards[currentMove]);
5548 MovePV (int x, int y, int h)
5549 { // step through PV based on mouse coordinates (called on mouse move)
5550 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5552 // we must somehow check if right button is still down (might be released off board!)
5553 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5554 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5555 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5557 lastX = x; lastY = y;
5559 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5560 if(endPV < 0) return;
5561 if(y < margin) step = 1; else
5562 if(y > h - margin) step = -1;
5563 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5564 currentMove += step;
5565 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5566 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5567 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5568 DrawPosition(FALSE, boards[currentMove]);
5572 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5573 // All positions will have equal probability, but the current method will not provide a unique
5574 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5580 int piecesLeft[(int)BlackPawn];
5581 int seed, nrOfShuffles;
5584 GetPositionNumber ()
5585 { // sets global variable seed
5588 seed = appData.defaultFrcPosition;
5589 if(seed < 0) { // randomize based on time for negative FRC position numbers
5590 for(i=0; i<50; i++) seed += random();
5591 seed = random() ^ random() >> 8 ^ random() << 8;
5592 if(seed<0) seed = -seed;
5597 put (Board board, int pieceType, int rank, int n, int shade)
5598 // put the piece on the (n-1)-th empty squares of the given shade
5602 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5603 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5604 board[rank][i] = (ChessSquare) pieceType;
5605 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5607 piecesLeft[pieceType]--;
5616 AddOnePiece (Board board, int pieceType, int rank, int shade)
5617 // calculate where the next piece goes, (any empty square), and put it there
5621 i = seed % squaresLeft[shade];
5622 nrOfShuffles *= squaresLeft[shade];
5623 seed /= squaresLeft[shade];
5624 put(board, pieceType, rank, i, shade);
5628 AddTwoPieces (Board board, int pieceType, int rank)
5629 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5631 int i, n=squaresLeft[ANY], j=n-1, k;
5633 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5634 i = seed % k; // pick one
5637 while(i >= j) i -= j--;
5638 j = n - 1 - j; i += j;
5639 put(board, pieceType, rank, j, ANY);
5640 put(board, pieceType, rank, i, ANY);
5644 SetUpShuffle (Board board, int number)
5648 GetPositionNumber(); nrOfShuffles = 1;
5650 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5651 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5652 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5654 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5656 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5657 p = (int) board[0][i];
5658 if(p < (int) BlackPawn) piecesLeft[p] ++;
5659 board[0][i] = EmptySquare;
5662 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5663 // shuffles restricted to allow normal castling put KRR first
5664 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5665 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5666 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5667 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5668 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5669 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5670 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5671 put(board, WhiteRook, 0, 0, ANY);
5672 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5675 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5676 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5677 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5678 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5679 while(piecesLeft[p] >= 2) {
5680 AddOnePiece(board, p, 0, LITE);
5681 AddOnePiece(board, p, 0, DARK);
5683 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5686 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5687 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5688 // but we leave King and Rooks for last, to possibly obey FRC restriction
5689 if(p == (int)WhiteRook) continue;
5690 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5691 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5694 // now everything is placed, except perhaps King (Unicorn) and Rooks
5696 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5697 // Last King gets castling rights
5698 while(piecesLeft[(int)WhiteUnicorn]) {
5699 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5700 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5703 while(piecesLeft[(int)WhiteKing]) {
5704 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5705 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5710 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5711 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5714 // Only Rooks can be left; simply place them all
5715 while(piecesLeft[(int)WhiteRook]) {
5716 i = put(board, WhiteRook, 0, 0, ANY);
5717 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5720 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5722 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5725 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5726 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5729 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5733 SetCharTable (char *table, const char * map)
5734 /* [HGM] moved here from winboard.c because of its general usefulness */
5735 /* Basically a safe strcpy that uses the last character as King */
5737 int result = FALSE; int NrPieces;
5739 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5740 && NrPieces >= 12 && !(NrPieces&1)) {
5741 int i; /* [HGM] Accept even length from 12 to 34 */
5743 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5744 for( i=0; i<NrPieces/2-1; i++ ) {
5746 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5748 table[(int) WhiteKing] = map[NrPieces/2-1];
5749 table[(int) BlackKing] = map[NrPieces-1];
5758 Prelude (Board board)
5759 { // [HGM] superchess: random selection of exo-pieces
5760 int i, j, k; ChessSquare p;
5761 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5763 GetPositionNumber(); // use FRC position number
5765 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5766 SetCharTable(pieceToChar, appData.pieceToCharTable);
5767 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5768 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5771 j = seed%4; seed /= 4;
5772 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5773 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5774 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5775 j = seed%3 + (seed%3 >= j); seed /= 3;
5776 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5777 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5778 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5779 j = seed%3; seed /= 3;
5780 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5781 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5782 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5783 j = seed%2 + (seed%2 >= j); seed /= 2;
5784 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5785 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5786 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5787 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5788 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5789 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5790 put(board, exoPieces[0], 0, 0, ANY);
5791 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5795 InitPosition (int redraw)
5797 ChessSquare (* pieces)[BOARD_FILES];
5798 int i, j, pawnRow, overrule,
5799 oldx = gameInfo.boardWidth,
5800 oldy = gameInfo.boardHeight,
5801 oldh = gameInfo.holdingsWidth;
5804 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5806 /* [AS] Initialize pv info list [HGM] and game status */
5808 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5809 pvInfoList[i].depth = 0;
5810 boards[i][EP_STATUS] = EP_NONE;
5811 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5814 initialRulePlies = 0; /* 50-move counter start */
5816 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5817 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5821 /* [HGM] logic here is completely changed. In stead of full positions */
5822 /* the initialized data only consist of the two backranks. The switch */
5823 /* selects which one we will use, which is than copied to the Board */
5824 /* initialPosition, which for the rest is initialized by Pawns and */
5825 /* empty squares. This initial position is then copied to boards[0], */
5826 /* possibly after shuffling, so that it remains available. */
5828 gameInfo.holdingsWidth = 0; /* default board sizes */
5829 gameInfo.boardWidth = 8;
5830 gameInfo.boardHeight = 8;
5831 gameInfo.holdingsSize = 0;
5832 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5833 for(i=0; i<BOARD_FILES-2; i++)
5834 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5835 initialPosition[EP_STATUS] = EP_NONE;
5836 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5837 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5838 SetCharTable(pieceNickName, appData.pieceNickNames);
5839 else SetCharTable(pieceNickName, "............");
5842 switch (gameInfo.variant) {
5843 case VariantFischeRandom:
5844 shuffleOpenings = TRUE;
5847 case VariantShatranj:
5848 pieces = ShatranjArray;
5849 nrCastlingRights = 0;
5850 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5853 pieces = makrukArray;
5854 nrCastlingRights = 0;
5855 startedFromSetupPosition = TRUE;
5856 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5858 case VariantTwoKings:
5859 pieces = twoKingsArray;
5862 pieces = GrandArray;
5863 nrCastlingRights = 0;
5864 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865 gameInfo.boardWidth = 10;
5866 gameInfo.boardHeight = 10;
5867 gameInfo.holdingsSize = 7;
5869 case VariantCapaRandom:
5870 shuffleOpenings = TRUE;
5871 case VariantCapablanca:
5872 pieces = CapablancaArray;
5873 gameInfo.boardWidth = 10;
5874 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5877 pieces = GothicArray;
5878 gameInfo.boardWidth = 10;
5879 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5882 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5883 gameInfo.holdingsSize = 7;
5884 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5887 pieces = JanusArray;
5888 gameInfo.boardWidth = 10;
5889 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5890 nrCastlingRights = 6;
5891 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5892 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5893 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5894 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5895 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5896 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5899 pieces = FalconArray;
5900 gameInfo.boardWidth = 10;
5901 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5903 case VariantXiangqi:
5904 pieces = XiangqiArray;
5905 gameInfo.boardWidth = 9;
5906 gameInfo.boardHeight = 10;
5907 nrCastlingRights = 0;
5908 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5911 pieces = ShogiArray;
5912 gameInfo.boardWidth = 9;
5913 gameInfo.boardHeight = 9;
5914 gameInfo.holdingsSize = 7;
5915 nrCastlingRights = 0;
5916 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5918 case VariantCourier:
5919 pieces = CourierArray;
5920 gameInfo.boardWidth = 12;
5921 nrCastlingRights = 0;
5922 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5924 case VariantKnightmate:
5925 pieces = KnightmateArray;
5926 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5928 case VariantSpartan:
5929 pieces = SpartanArray;
5930 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5933 pieces = fairyArray;
5934 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5937 pieces = GreatArray;
5938 gameInfo.boardWidth = 10;
5939 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5940 gameInfo.holdingsSize = 8;
5944 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5945 gameInfo.holdingsSize = 8;
5946 startedFromSetupPosition = TRUE;
5948 case VariantCrazyhouse:
5949 case VariantBughouse:
5951 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5952 gameInfo.holdingsSize = 5;
5954 case VariantWildCastle:
5956 /* !!?shuffle with kings guaranteed to be on d or e file */
5957 shuffleOpenings = 1;
5959 case VariantNoCastle:
5961 nrCastlingRights = 0;
5962 /* !!?unconstrained back-rank shuffle */
5963 shuffleOpenings = 1;
5968 if(appData.NrFiles >= 0) {
5969 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5970 gameInfo.boardWidth = appData.NrFiles;
5972 if(appData.NrRanks >= 0) {
5973 gameInfo.boardHeight = appData.NrRanks;
5975 if(appData.holdingsSize >= 0) {
5976 i = appData.holdingsSize;
5977 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5978 gameInfo.holdingsSize = i;
5980 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5981 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5982 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5984 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5985 if(pawnRow < 1) pawnRow = 1;
5986 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5988 /* User pieceToChar list overrules defaults */
5989 if(appData.pieceToCharTable != NULL)
5990 SetCharTable(pieceToChar, appData.pieceToCharTable);
5992 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5994 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5995 s = (ChessSquare) 0; /* account holding counts in guard band */
5996 for( i=0; i<BOARD_HEIGHT; i++ )
5997 initialPosition[i][j] = s;
5999 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6000 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6001 initialPosition[pawnRow][j] = WhitePawn;
6002 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6003 if(gameInfo.variant == VariantXiangqi) {
6005 initialPosition[pawnRow][j] =
6006 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6007 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6008 initialPosition[2][j] = WhiteCannon;
6009 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6013 if(gameInfo.variant == VariantGrand) {
6014 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6015 initialPosition[0][j] = WhiteRook;
6016 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6019 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6021 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6024 initialPosition[1][j] = WhiteBishop;
6025 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6027 initialPosition[1][j] = WhiteRook;
6028 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6031 if( nrCastlingRights == -1) {
6032 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6033 /* This sets default castling rights from none to normal corners */
6034 /* Variants with other castling rights must set them themselves above */
6035 nrCastlingRights = 6;
6037 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6038 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6039 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6040 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6041 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6042 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6045 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6046 if(gameInfo.variant == VariantGreat) { // promotion commoners
6047 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6048 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6049 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6050 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6052 if( gameInfo.variant == VariantSChess ) {
6053 initialPosition[1][0] = BlackMarshall;
6054 initialPosition[2][0] = BlackAngel;
6055 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6056 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6057 initialPosition[1][1] = initialPosition[2][1] =
6058 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6060 if (appData.debugMode) {
6061 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6063 if(shuffleOpenings) {
6064 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6065 startedFromSetupPosition = TRUE;
6067 if(startedFromPositionFile) {
6068 /* [HGM] loadPos: use PositionFile for every new game */
6069 CopyBoard(initialPosition, filePosition);
6070 for(i=0; i<nrCastlingRights; i++)
6071 initialRights[i] = filePosition[CASTLING][i];
6072 startedFromSetupPosition = TRUE;
6075 CopyBoard(boards[0], initialPosition);
6077 if(oldx != gameInfo.boardWidth ||
6078 oldy != gameInfo.boardHeight ||
6079 oldv != gameInfo.variant ||
6080 oldh != gameInfo.holdingsWidth
6082 InitDrawingSizes(-2 ,0);
6084 oldv = gameInfo.variant;
6086 DrawPosition(TRUE, boards[currentMove]);
6090 SendBoard (ChessProgramState *cps, int moveNum)
6092 char message[MSG_SIZ];
6094 if (cps->useSetboard) {
6095 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6096 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6097 SendToProgram(message, cps);
6102 int i, j, left=0, right=BOARD_WIDTH;
6103 /* Kludge to set black to move, avoiding the troublesome and now
6104 * deprecated "black" command.
6106 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6107 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6109 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6111 SendToProgram("edit\n", cps);
6112 SendToProgram("#\n", cps);
6113 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6114 bp = &boards[moveNum][i][left];
6115 for (j = left; j < right; j++, bp++) {
6116 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6117 if ((int) *bp < (int) BlackPawn) {
6118 if(j == BOARD_RGHT+1)
6119 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6120 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6121 if(message[0] == '+' || message[0] == '~') {
6122 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6123 PieceToChar((ChessSquare)(DEMOTED *bp)),
6126 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6127 message[1] = BOARD_RGHT - 1 - j + '1';
6128 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6130 SendToProgram(message, cps);
6135 SendToProgram("c\n", cps);
6136 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6137 bp = &boards[moveNum][i][left];
6138 for (j = left; j < right; j++, bp++) {
6139 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6140 if (((int) *bp != (int) EmptySquare)
6141 && ((int) *bp >= (int) BlackPawn)) {
6142 if(j == BOARD_LEFT-2)
6143 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6144 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6146 if(message[0] == '+' || message[0] == '~') {
6147 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6148 PieceToChar((ChessSquare)(DEMOTED *bp)),
6151 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6152 message[1] = BOARD_RGHT - 1 - j + '1';
6153 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6155 SendToProgram(message, cps);
6160 SendToProgram(".\n", cps);
6162 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6165 char exclusionHeader[MSG_SIZ];
6166 int exCnt, excludePtr;
6167 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6168 static Exclusion excluTab[200];
6169 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6175 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6176 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6182 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6183 excludePtr = 24; exCnt = 0;
6188 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6189 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6190 char buf[2*MOVE_LEN], *p;
6191 Exclusion *e = excluTab;
6193 for(i=0; i<exCnt; i++)
6194 if(e[i].ff == fromX && e[i].fr == fromY &&
6195 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6196 if(i == exCnt) { // was not in exclude list; add it
6197 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6198 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6199 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6202 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6203 excludePtr++; e[i].mark = excludePtr++;
6204 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6207 exclusionHeader[e[i].mark] = state;
6211 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6212 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6216 if((signed char)promoChar == -1) { // kludge to indicate best move
6217 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6218 return 1; // if unparsable, abort
6220 // update exclusion map (resolving toggle by consulting existing state)
6221 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6223 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6224 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6225 excludeMap[k] |= 1<<j;
6226 else excludeMap[k] &= ~(1<<j);
6228 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6230 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6231 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6233 return (state == '+');
6237 ExcludeClick (int index)
6240 Exclusion *e = excluTab;
6241 if(index < 25) { // none, best or tail clicked
6242 if(index < 13) { // none: include all
6243 WriteMap(0); // clear map
6244 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6245 SendToBoth("include all\n"); // and inform engine
6246 } else if(index > 18) { // tail
6247 if(exclusionHeader[19] == '-') { // tail was excluded
6248 SendToBoth("include all\n");
6249 WriteMap(0); // clear map completely
6250 // now re-exclude selected moves
6251 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6252 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6253 } else { // tail was included or in mixed state
6254 SendToBoth("exclude all\n");
6255 WriteMap(0xFF); // fill map completely
6256 // now re-include selected moves
6257 j = 0; // count them
6258 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6259 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6260 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6263 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6266 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6267 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6268 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6275 DefaultPromoChoice (int white)
6278 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6279 result = WhiteFerz; // no choice
6280 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6281 result= WhiteKing; // in Suicide Q is the last thing we want
6282 else if(gameInfo.variant == VariantSpartan)
6283 result = white ? WhiteQueen : WhiteAngel;
6284 else result = WhiteQueen;
6285 if(!white) result = WHITE_TO_BLACK result;
6289 static int autoQueen; // [HGM] oneclick
6292 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6294 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6295 /* [HGM] add Shogi promotions */
6296 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6301 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6302 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6304 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6305 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6308 piece = boards[currentMove][fromY][fromX];
6309 if(gameInfo.variant == VariantShogi) {
6310 promotionZoneSize = BOARD_HEIGHT/3;
6311 highestPromotingPiece = (int)WhiteFerz;
6312 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6313 promotionZoneSize = 3;
6316 // Treat Lance as Pawn when it is not representing Amazon
6317 if(gameInfo.variant != VariantSuper) {
6318 if(piece == WhiteLance) piece = WhitePawn; else
6319 if(piece == BlackLance) piece = BlackPawn;
6322 // next weed out all moves that do not touch the promotion zone at all
6323 if((int)piece >= BlackPawn) {
6324 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6326 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6328 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6329 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6332 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6334 // weed out mandatory Shogi promotions
6335 if(gameInfo.variant == VariantShogi) {
6336 if(piece >= BlackPawn) {
6337 if(toY == 0 && piece == BlackPawn ||
6338 toY == 0 && piece == BlackQueen ||
6339 toY <= 1 && piece == BlackKnight) {
6344 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6345 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6346 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6353 // weed out obviously illegal Pawn moves
6354 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6355 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6356 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6357 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6358 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6359 // note we are not allowed to test for valid (non-)capture, due to premove
6362 // we either have a choice what to promote to, or (in Shogi) whether to promote
6363 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6364 *promoChoice = PieceToChar(BlackFerz); // no choice
6367 // no sense asking what we must promote to if it is going to explode...
6368 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6369 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6372 // give caller the default choice even if we will not make it
6373 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6374 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6375 if( sweepSelect && gameInfo.variant != VariantGreat
6376 && gameInfo.variant != VariantGrand
6377 && gameInfo.variant != VariantSuper) return FALSE;
6378 if(autoQueen) return FALSE; // predetermined
6380 // suppress promotion popup on illegal moves that are not premoves
6381 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6382 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6383 if(appData.testLegality && !premove) {
6384 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6385 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6386 if(moveType != WhitePromotion && moveType != BlackPromotion)
6394 InPalace (int row, int column)
6395 { /* [HGM] for Xiangqi */
6396 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6397 column < (BOARD_WIDTH + 4)/2 &&
6398 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6403 PieceForSquare (int x, int y)
6405 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6408 return boards[currentMove][y][x];
6412 OKToStartUserMove (int x, int y)
6414 ChessSquare from_piece;
6417 if (matchMode) return FALSE;
6418 if (gameMode == EditPosition) return TRUE;
6420 if (x >= 0 && y >= 0)
6421 from_piece = boards[currentMove][y][x];
6423 from_piece = EmptySquare;
6425 if (from_piece == EmptySquare) return FALSE;
6427 white_piece = (int)from_piece >= (int)WhitePawn &&
6428 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6432 case TwoMachinesPlay:
6440 case MachinePlaysWhite:
6441 case IcsPlayingBlack:
6442 if (appData.zippyPlay) return FALSE;
6444 DisplayMoveError(_("You are playing Black"));
6449 case MachinePlaysBlack:
6450 case IcsPlayingWhite:
6451 if (appData.zippyPlay) return FALSE;
6453 DisplayMoveError(_("You are playing White"));
6458 case PlayFromGameFile:
6459 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6461 if (!white_piece && WhiteOnMove(currentMove)) {
6462 DisplayMoveError(_("It is White's turn"));
6465 if (white_piece && !WhiteOnMove(currentMove)) {
6466 DisplayMoveError(_("It is Black's turn"));
6469 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6470 /* Editing correspondence game history */
6471 /* Could disallow this or prompt for confirmation */
6476 case BeginningOfGame:
6477 if (appData.icsActive) return FALSE;
6478 if (!appData.noChessProgram) {
6480 DisplayMoveError(_("You are playing White"));
6487 if (!white_piece && WhiteOnMove(currentMove)) {
6488 DisplayMoveError(_("It is White's turn"));
6491 if (white_piece && !WhiteOnMove(currentMove)) {
6492 DisplayMoveError(_("It is Black's turn"));
6501 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6502 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6503 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6504 && gameMode != AnalyzeFile && gameMode != Training) {
6505 DisplayMoveError(_("Displayed position is not current"));
6512 OnlyMove (int *x, int *y, Boolean captures)
6514 DisambiguateClosure cl;
6515 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6517 case MachinePlaysBlack:
6518 case IcsPlayingWhite:
6519 case BeginningOfGame:
6520 if(!WhiteOnMove(currentMove)) return FALSE;
6522 case MachinePlaysWhite:
6523 case IcsPlayingBlack:
6524 if(WhiteOnMove(currentMove)) return FALSE;
6531 cl.pieceIn = EmptySquare;
6536 cl.promoCharIn = NULLCHAR;
6537 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6538 if( cl.kind == NormalMove ||
6539 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6540 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6541 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6548 if(cl.kind != ImpossibleMove) return FALSE;
6549 cl.pieceIn = EmptySquare;
6554 cl.promoCharIn = NULLCHAR;
6555 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6556 if( cl.kind == NormalMove ||
6557 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6558 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6559 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6564 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6570 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6571 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6572 int lastLoadGameUseList = FALSE;
6573 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6574 ChessMove lastLoadGameStart = EndOfFile;
6578 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6582 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6584 /* Check if the user is playing in turn. This is complicated because we
6585 let the user "pick up" a piece before it is his turn. So the piece he
6586 tried to pick up may have been captured by the time he puts it down!
6587 Therefore we use the color the user is supposed to be playing in this
6588 test, not the color of the piece that is currently on the starting
6589 square---except in EditGame mode, where the user is playing both
6590 sides; fortunately there the capture race can't happen. (It can
6591 now happen in IcsExamining mode, but that's just too bad. The user
6592 will get a somewhat confusing message in that case.)
6597 case TwoMachinesPlay:
6601 /* We switched into a game mode where moves are not accepted,
6602 perhaps while the mouse button was down. */
6605 case MachinePlaysWhite:
6606 /* User is moving for Black */
6607 if (WhiteOnMove(currentMove)) {
6608 DisplayMoveError(_("It is White's turn"));
6613 case MachinePlaysBlack:
6614 /* User is moving for White */
6615 if (!WhiteOnMove(currentMove)) {
6616 DisplayMoveError(_("It is Black's turn"));
6621 case PlayFromGameFile:
6622 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6625 case BeginningOfGame:
6628 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6629 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6630 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6631 /* User is moving for Black */
6632 if (WhiteOnMove(currentMove)) {
6633 DisplayMoveError(_("It is White's turn"));
6637 /* User is moving for White */
6638 if (!WhiteOnMove(currentMove)) {
6639 DisplayMoveError(_("It is Black's turn"));
6645 case IcsPlayingBlack:
6646 /* User is moving for Black */
6647 if (WhiteOnMove(currentMove)) {
6648 if (!appData.premove) {
6649 DisplayMoveError(_("It is White's turn"));
6650 } else if (toX >= 0 && toY >= 0) {
6653 premoveFromX = fromX;
6654 premoveFromY = fromY;
6655 premovePromoChar = promoChar;
6657 if (appData.debugMode)
6658 fprintf(debugFP, "Got premove: fromX %d,"
6659 "fromY %d, toX %d, toY %d\n",
6660 fromX, fromY, toX, toY);
6666 case IcsPlayingWhite:
6667 /* User is moving for White */
6668 if (!WhiteOnMove(currentMove)) {
6669 if (!appData.premove) {
6670 DisplayMoveError(_("It is Black's turn"));
6671 } else if (toX >= 0 && toY >= 0) {
6674 premoveFromX = fromX;
6675 premoveFromY = fromY;
6676 premovePromoChar = promoChar;
6678 if (appData.debugMode)
6679 fprintf(debugFP, "Got premove: fromX %d,"
6680 "fromY %d, toX %d, toY %d\n",
6681 fromX, fromY, toX, toY);
6691 /* EditPosition, empty square, or different color piece;
6692 click-click move is possible */
6693 if (toX == -2 || toY == -2) {
6694 boards[0][fromY][fromX] = EmptySquare;
6695 DrawPosition(FALSE, boards[currentMove]);
6697 } else if (toX >= 0 && toY >= 0) {
6698 boards[0][toY][toX] = boards[0][fromY][fromX];
6699 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6700 if(boards[0][fromY][0] != EmptySquare) {
6701 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6702 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6705 if(fromX == BOARD_RGHT+1) {
6706 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6707 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6708 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6711 boards[0][fromY][fromX] = gatingPiece;
6712 DrawPosition(FALSE, boards[currentMove]);
6718 if(toX < 0 || toY < 0) return;
6719 pup = boards[currentMove][toY][toX];
6721 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6722 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6723 if( pup != EmptySquare ) return;
6724 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6725 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6726 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6727 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6728 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6729 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6730 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6734 /* [HGM] always test for legality, to get promotion info */
6735 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6736 fromY, fromX, toY, toX, promoChar);
6738 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6740 /* [HGM] but possibly ignore an IllegalMove result */
6741 if (appData.testLegality) {
6742 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6743 DisplayMoveError(_("Illegal move"));
6748 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6749 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6750 ClearPremoveHighlights(); // was included
6751 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6755 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6758 /* Common tail of UserMoveEvent and DropMenuEvent */
6760 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6764 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6765 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6766 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6767 if(WhiteOnMove(currentMove)) {
6768 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6770 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6774 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6775 move type in caller when we know the move is a legal promotion */
6776 if(moveType == NormalMove && promoChar)
6777 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6779 /* [HGM] <popupFix> The following if has been moved here from
6780 UserMoveEvent(). Because it seemed to belong here (why not allow
6781 piece drops in training games?), and because it can only be
6782 performed after it is known to what we promote. */
6783 if (gameMode == Training) {
6784 /* compare the move played on the board to the next move in the
6785 * game. If they match, display the move and the opponent's response.
6786 * If they don't match, display an error message.
6790 CopyBoard(testBoard, boards[currentMove]);
6791 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6793 if (CompareBoards(testBoard, boards[currentMove+1])) {
6794 ForwardInner(currentMove+1);
6796 /* Autoplay the opponent's response.
6797 * if appData.animate was TRUE when Training mode was entered,
6798 * the response will be animated.
6800 saveAnimate = appData.animate;
6801 appData.animate = animateTraining;
6802 ForwardInner(currentMove+1);
6803 appData.animate = saveAnimate;
6805 /* check for the end of the game */
6806 if (currentMove >= forwardMostMove) {
6807 gameMode = PlayFromGameFile;
6809 SetTrainingModeOff();
6810 DisplayInformation(_("End of game"));
6813 DisplayError(_("Incorrect move"), 0);
6818 /* Ok, now we know that the move is good, so we can kill
6819 the previous line in Analysis Mode */
6820 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6821 && currentMove < forwardMostMove) {
6822 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6823 else forwardMostMove = currentMove;
6828 /* If we need the chess program but it's dead, restart it */
6829 ResurrectChessProgram();
6831 /* A user move restarts a paused game*/
6835 thinkOutput[0] = NULLCHAR;
6837 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6839 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6840 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6844 if (gameMode == BeginningOfGame) {
6845 if (appData.noChessProgram) {
6846 gameMode = EditGame;
6850 gameMode = MachinePlaysBlack;
6853 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6855 if (first.sendName) {
6856 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6857 SendToProgram(buf, &first);
6864 /* Relay move to ICS or chess engine */
6865 if (appData.icsActive) {
6866 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6867 gameMode == IcsExamining) {
6868 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6869 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6871 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6873 // also send plain move, in case ICS does not understand atomic claims
6874 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6878 if (first.sendTime && (gameMode == BeginningOfGame ||
6879 gameMode == MachinePlaysWhite ||
6880 gameMode == MachinePlaysBlack)) {
6881 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6883 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6884 // [HGM] book: if program might be playing, let it use book
6885 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6886 first.maybeThinking = TRUE;
6887 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6888 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6889 SendBoard(&first, currentMove+1);
6890 if(second.analyzing) {
6891 if(!second.useSetboard) SendToProgram("undo\n", &second);
6892 SendBoard(&second, currentMove+1);
6895 SendMoveToProgram(forwardMostMove-1, &first);
6896 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6898 if (currentMove == cmailOldMove + 1) {
6899 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6903 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6907 if(appData.testLegality)
6908 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6914 if (WhiteOnMove(currentMove)) {
6915 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6917 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6921 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6926 case MachinePlaysBlack:
6927 case MachinePlaysWhite:
6928 /* disable certain menu options while machine is thinking */
6929 SetMachineThinkingEnables();
6936 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6937 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6939 if(bookHit) { // [HGM] book: simulate book reply
6940 static char bookMove[MSG_SIZ]; // a bit generous?
6942 programStats.nodes = programStats.depth = programStats.time =
6943 programStats.score = programStats.got_only_move = 0;
6944 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6946 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6947 strcat(bookMove, bookHit);
6948 HandleMachineMove(bookMove, &first);
6954 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6956 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6957 Markers *m = (Markers *) closure;
6958 if(rf == fromY && ff == fromX)
6959 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6960 || kind == WhiteCapturesEnPassant
6961 || kind == BlackCapturesEnPassant);
6962 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6966 MarkTargetSquares (int clear)
6969 if(clear) // no reason to ever suppress clearing
6970 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6971 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6972 !appData.testLegality || gameMode == EditPosition) return;
6975 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6976 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6977 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6979 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6982 DrawPosition(FALSE, NULL);
6986 Explode (Board board, int fromX, int fromY, int toX, int toY)
6988 if(gameInfo.variant == VariantAtomic &&
6989 (board[toY][toX] != EmptySquare || // capture?
6990 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6991 board[fromY][fromX] == BlackPawn )
6993 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6999 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7002 CanPromote (ChessSquare piece, int y)
7004 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7005 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7006 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7007 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7008 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7009 gameInfo.variant == VariantMakruk) return FALSE;
7010 return (piece == BlackPawn && y == 1 ||
7011 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7012 piece == BlackLance && y == 1 ||
7013 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7017 LeftClick (ClickType clickType, int xPix, int yPix)
7020 Boolean saveAnimate;
7021 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7022 char promoChoice = NULLCHAR;
7024 static TimeMark lastClickTime, prevClickTime;
7026 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7028 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7030 if (clickType == Press) ErrorPopDown();
7032 x = EventToSquare(xPix, BOARD_WIDTH);
7033 y = EventToSquare(yPix, BOARD_HEIGHT);
7034 if (!flipView && y >= 0) {
7035 y = BOARD_HEIGHT - 1 - y;
7037 if (flipView && x >= 0) {
7038 x = BOARD_WIDTH - 1 - x;
7041 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7042 defaultPromoChoice = promoSweep;
7043 promoSweep = EmptySquare; // terminate sweep
7044 promoDefaultAltered = TRUE;
7045 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7048 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7049 if(clickType == Release) return; // ignore upclick of click-click destination
7050 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7051 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7052 if(gameInfo.holdingsWidth &&
7053 (WhiteOnMove(currentMove)
7054 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7055 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7056 // click in right holdings, for determining promotion piece
7057 ChessSquare p = boards[currentMove][y][x];
7058 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7059 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7060 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7061 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7066 DrawPosition(FALSE, boards[currentMove]);
7070 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7071 if(clickType == Press
7072 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7073 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7074 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7077 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7078 // could be static click on premove from-square: abort premove
7080 ClearPremoveHighlights();
7083 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7084 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7086 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7087 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7088 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7089 defaultPromoChoice = DefaultPromoChoice(side);
7092 autoQueen = appData.alwaysPromoteToQueen;
7096 gatingPiece = EmptySquare;
7097 if (clickType != Press) {
7098 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7099 DragPieceEnd(xPix, yPix); dragging = 0;
7100 DrawPosition(FALSE, NULL);
7104 doubleClick = FALSE;
7105 if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7106 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7108 fromX = x; fromY = y; toX = toY = -1;
7109 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7110 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7111 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7113 if (OKToStartUserMove(fromX, fromY)) {
7115 MarkTargetSquares(0);
7116 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7117 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7118 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7119 promoSweep = defaultPromoChoice;
7120 selectFlag = 0; lastX = xPix; lastY = yPix;
7121 Sweep(0); // Pawn that is going to promote: preview promotion piece
7122 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7124 if (appData.highlightDragging) {
7125 SetHighlights(fromX, fromY, -1, -1);
7129 } else fromX = fromY = -1;
7135 if (clickType == Press && gameMode != EditPosition) {
7140 // ignore off-board to clicks
7141 if(y < 0 || x < 0) return;
7143 /* Check if clicking again on the same color piece */
7144 fromP = boards[currentMove][fromY][fromX];
7145 toP = boards[currentMove][y][x];
7146 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7147 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7148 WhitePawn <= toP && toP <= WhiteKing &&
7149 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7150 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7151 (BlackPawn <= fromP && fromP <= BlackKing &&
7152 BlackPawn <= toP && toP <= BlackKing &&
7153 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7154 !(fromP == BlackKing && toP == BlackRook && frc))) {
7155 /* Clicked again on same color piece -- changed his mind */
7156 second = (x == fromX && y == fromY);
7157 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7158 second = FALSE; // first double-click rather than scond click
7159 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7161 promoDefaultAltered = FALSE;
7162 MarkTargetSquares(1);
7163 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7164 if (appData.highlightDragging) {
7165 SetHighlights(x, y, -1, -1);
7169 if (OKToStartUserMove(x, y)) {
7170 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7171 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7172 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7173 gatingPiece = boards[currentMove][fromY][fromX];
7174 else gatingPiece = doubleClick ? fromP : EmptySquare;
7176 fromY = y; dragging = 1;
7177 MarkTargetSquares(0);
7178 DragPieceBegin(xPix, yPix, FALSE);
7179 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7180 promoSweep = defaultPromoChoice;
7181 selectFlag = 0; lastX = xPix; lastY = yPix;
7182 Sweep(0); // Pawn that is going to promote: preview promotion piece
7186 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7189 // ignore clicks on holdings
7190 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7193 if (clickType == Release && x == fromX && y == fromY) {
7194 DragPieceEnd(xPix, yPix); dragging = 0;
7196 // a deferred attempt to click-click move an empty square on top of a piece
7197 boards[currentMove][y][x] = EmptySquare;
7199 DrawPosition(FALSE, boards[currentMove]);
7200 fromX = fromY = -1; clearFlag = 0;
7203 if (appData.animateDragging) {
7204 /* Undo animation damage if any */
7205 DrawPosition(FALSE, NULL);
7207 if (second || sweepSelecting) {
7208 /* Second up/down in same square; just abort move */
7209 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7210 second = sweepSelecting = 0;
7212 gatingPiece = EmptySquare;
7215 ClearPremoveHighlights();
7217 /* First upclick in same square; start click-click mode */
7218 SetHighlights(x, y, -1, -1);
7225 /* we now have a different from- and (possibly off-board) to-square */
7226 /* Completed move */
7227 if(!sweepSelecting) {
7230 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7232 saveAnimate = appData.animate;
7233 if (clickType == Press) {
7234 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7235 // must be Edit Position mode with empty-square selected
7236 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7237 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7240 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7241 if(appData.sweepSelect) {
7242 ChessSquare piece = boards[currentMove][fromY][fromX];
7243 promoSweep = defaultPromoChoice;
7244 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7245 selectFlag = 0; lastX = xPix; lastY = yPix;
7246 Sweep(0); // Pawn that is going to promote: preview promotion piece
7248 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7249 MarkTargetSquares(1);
7251 return; // promo popup appears on up-click
7253 /* Finish clickclick move */
7254 if (appData.animate || appData.highlightLastMove) {
7255 SetHighlights(fromX, fromY, toX, toY);
7260 /* Finish drag move */
7261 if (appData.highlightLastMove) {
7262 SetHighlights(fromX, fromY, toX, toY);
7266 DragPieceEnd(xPix, yPix); dragging = 0;
7267 /* Don't animate move and drag both */
7268 appData.animate = FALSE;
7270 MarkTargetSquares(1);
7272 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7273 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7274 ChessSquare piece = boards[currentMove][fromY][fromX];
7275 if(gameMode == EditPosition && piece != EmptySquare &&
7276 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7279 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7280 n = PieceToNumber(piece - (int)BlackPawn);
7281 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7282 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7283 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7285 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7286 n = PieceToNumber(piece);
7287 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7288 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7289 boards[currentMove][n][BOARD_WIDTH-2]++;
7291 boards[currentMove][fromY][fromX] = EmptySquare;
7295 DrawPosition(TRUE, boards[currentMove]);
7299 // off-board moves should not be highlighted
7300 if(x < 0 || y < 0) ClearHighlights();
7302 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7304 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7305 SetHighlights(fromX, fromY, toX, toY);
7306 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7307 // [HGM] super: promotion to captured piece selected from holdings
7308 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7309 promotionChoice = TRUE;
7310 // kludge follows to temporarily execute move on display, without promoting yet
7311 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7312 boards[currentMove][toY][toX] = p;
7313 DrawPosition(FALSE, boards[currentMove]);
7314 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7315 boards[currentMove][toY][toX] = q;
7316 DisplayMessage("Click in holdings to choose piece", "");
7321 int oldMove = currentMove;
7322 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7323 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7324 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7325 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7326 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7327 DrawPosition(TRUE, boards[currentMove]);
7330 appData.animate = saveAnimate;
7331 if (appData.animate || appData.animateDragging) {
7332 /* Undo animation damage if needed */
7333 DrawPosition(FALSE, NULL);
7338 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7339 { // front-end-free part taken out of PieceMenuPopup
7340 int whichMenu; int xSqr, ySqr;
7342 if(seekGraphUp) { // [HGM] seekgraph
7343 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7344 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7348 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7349 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7350 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7351 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7352 if(action == Press) {
7353 originalFlip = flipView;
7354 flipView = !flipView; // temporarily flip board to see game from partners perspective
7355 DrawPosition(TRUE, partnerBoard);
7356 DisplayMessage(partnerStatus, "");
7358 } else if(action == Release) {
7359 flipView = originalFlip;
7360 DrawPosition(TRUE, boards[currentMove]);
7366 xSqr = EventToSquare(x, BOARD_WIDTH);
7367 ySqr = EventToSquare(y, BOARD_HEIGHT);
7368 if (action == Release) {
7369 if(pieceSweep != EmptySquare) {
7370 EditPositionMenuEvent(pieceSweep, toX, toY);
7371 pieceSweep = EmptySquare;
7372 } else UnLoadPV(); // [HGM] pv
7374 if (action != Press) return -2; // return code to be ignored
7377 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7379 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7380 if (xSqr < 0 || ySqr < 0) return -1;
7381 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7382 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7383 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7384 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7388 if(!appData.icsEngineAnalyze) return -1;
7389 case IcsPlayingWhite:
7390 case IcsPlayingBlack:
7391 if(!appData.zippyPlay) goto noZip;
7394 case MachinePlaysWhite:
7395 case MachinePlaysBlack:
7396 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7397 if (!appData.dropMenu) {
7399 return 2; // flag front-end to grab mouse events
7401 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7402 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7405 if (xSqr < 0 || ySqr < 0) return -1;
7406 if (!appData.dropMenu || appData.testLegality &&
7407 gameInfo.variant != VariantBughouse &&
7408 gameInfo.variant != VariantCrazyhouse) return -1;
7409 whichMenu = 1; // drop menu
7415 if (((*fromX = xSqr) < 0) ||
7416 ((*fromY = ySqr) < 0)) {
7417 *fromX = *fromY = -1;
7421 *fromX = BOARD_WIDTH - 1 - *fromX;
7423 *fromY = BOARD_HEIGHT - 1 - *fromY;
7429 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7431 // char * hint = lastHint;
7432 FrontEndProgramStats stats;
7434 stats.which = cps == &first ? 0 : 1;
7435 stats.depth = cpstats->depth;
7436 stats.nodes = cpstats->nodes;
7437 stats.score = cpstats->score;
7438 stats.time = cpstats->time;
7439 stats.pv = cpstats->movelist;
7440 stats.hint = lastHint;
7441 stats.an_move_index = 0;
7442 stats.an_move_count = 0;
7444 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7445 stats.hint = cpstats->move_name;
7446 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7447 stats.an_move_count = cpstats->nr_moves;
7450 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
7452 SetProgramStats( &stats );
7456 ClearEngineOutputPane (int which)
7458 static FrontEndProgramStats dummyStats;
7459 dummyStats.which = which;
7460 dummyStats.pv = "#";
7461 SetProgramStats( &dummyStats );
7464 #define MAXPLAYERS 500
7467 TourneyStandings (int display)
7469 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7470 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7471 char result, *p, *names[MAXPLAYERS];
7473 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7474 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7475 names[0] = p = strdup(appData.participants);
7476 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7478 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7480 while(result = appData.results[nr]) {
7481 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7482 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7483 wScore = bScore = 0;
7485 case '+': wScore = 2; break;
7486 case '-': bScore = 2; break;
7487 case '=': wScore = bScore = 1; break;
7489 case '*': return strdup("busy"); // tourney not finished
7497 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7498 for(w=0; w<nPlayers; w++) {
7500 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7501 ranking[w] = b; points[w] = bScore; score[b] = -2;
7503 p = malloc(nPlayers*34+1);
7504 for(w=0; w<nPlayers && w<display; w++)
7505 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7511 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7512 { // count all piece types
7514 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7515 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7516 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7519 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7520 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7521 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7522 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7523 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7524 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7529 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7531 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7532 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7534 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7535 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7536 if(myPawns == 2 && nMine == 3) // KPP
7537 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7538 if(myPawns == 1 && nMine == 2) // KP
7539 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7540 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7541 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7542 if(myPawns) return FALSE;
7543 if(pCnt[WhiteRook+side])
7544 return pCnt[BlackRook-side] ||
7545 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7546 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7547 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7548 if(pCnt[WhiteCannon+side]) {
7549 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7550 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7552 if(pCnt[WhiteKnight+side])
7553 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7558 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7560 VariantClass v = gameInfo.variant;
7562 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7563 if(v == VariantShatranj) return TRUE; // always winnable through baring
7564 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7565 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7567 if(v == VariantXiangqi) {
7568 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7570 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7571 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7572 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7573 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7574 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7575 if(stale) // we have at least one last-rank P plus perhaps C
7576 return majors // KPKX
7577 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7579 return pCnt[WhiteFerz+side] // KCAK
7580 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7581 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7582 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7584 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7585 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7587 if(nMine == 1) return FALSE; // bare King
7588 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
7589 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7590 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7591 // by now we have King + 1 piece (or multiple Bishops on the same color)
7592 if(pCnt[WhiteKnight+side])
7593 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7594 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7595 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7597 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7598 if(pCnt[WhiteAlfil+side])
7599 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7600 if(pCnt[WhiteWazir+side])
7601 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7608 CompareWithRights (Board b1, Board b2)
7611 if(!CompareBoards(b1, b2)) return FALSE;
7612 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7613 /* compare castling rights */
7614 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7615 rights++; /* King lost rights, while rook still had them */
7616 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7617 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7618 rights++; /* but at least one rook lost them */
7620 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7622 if( b1[CASTLING][5] != NoRights ) {
7623 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7630 Adjudicate (ChessProgramState *cps)
7631 { // [HGM] some adjudications useful with buggy engines
7632 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7633 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7634 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7635 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7636 int k, count = 0; static int bare = 1;
7637 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7638 Boolean canAdjudicate = !appData.icsActive;
7640 // most tests only when we understand the game, i.e. legality-checking on
7641 if( appData.testLegality )
7642 { /* [HGM] Some more adjudications for obstinate engines */
7643 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7644 static int moveCount = 6;
7646 char *reason = NULL;
7648 /* Count what is on board. */
7649 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7651 /* Some material-based adjudications that have to be made before stalemate test */
7652 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7653 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7654 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7655 if(canAdjudicate && appData.checkMates) {
7657 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7658 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7659 "Xboard adjudication: King destroyed", GE_XBOARD );
7664 /* Bare King in Shatranj (loses) or Losers (wins) */
7665 if( nrW == 1 || nrB == 1) {
7666 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7667 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7668 if(canAdjudicate && appData.checkMates) {
7670 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7671 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7672 "Xboard adjudication: Bare king", GE_XBOARD );
7676 if( gameInfo.variant == VariantShatranj && --bare < 0)
7678 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7679 if(canAdjudicate && appData.checkMates) {
7680 /* but only adjudicate if adjudication enabled */
7682 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7683 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7684 "Xboard adjudication: Bare king", GE_XBOARD );
7691 // don't wait for engine to announce game end if we can judge ourselves
7692 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7694 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7695 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7696 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7697 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7700 reason = "Xboard adjudication: 3rd check";
7701 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7711 reason = "Xboard adjudication: Stalemate";
7712 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7713 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7714 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7715 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7716 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7717 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7718 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7719 EP_CHECKMATE : EP_WINS);
7720 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7721 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7725 reason = "Xboard adjudication: Checkmate";
7726 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7730 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7732 result = GameIsDrawn; break;
7734 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7736 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7740 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7742 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7743 GameEnds( result, reason, GE_XBOARD );
7747 /* Next absolutely insufficient mating material. */
7748 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7749 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7750 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7752 /* always flag draws, for judging claims */
7753 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7755 if(canAdjudicate && appData.materialDraws) {
7756 /* but only adjudicate them if adjudication enabled */
7757 if(engineOpponent) {
7758 SendToProgram("force\n", engineOpponent); // suppress reply
7759 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7761 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7766 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7767 if(gameInfo.variant == VariantXiangqi ?
7768 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7770 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7771 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7772 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7773 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7775 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7776 { /* if the first 3 moves do not show a tactical win, declare draw */
7777 if(engineOpponent) {
7778 SendToProgram("force\n", engineOpponent); // suppress reply
7779 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7781 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7784 } else moveCount = 6;
7787 // Repetition draws and 50-move rule can be applied independently of legality testing
7789 /* Check for rep-draws */
7791 for(k = forwardMostMove-2;
7792 k>=backwardMostMove && k>=forwardMostMove-100 &&
7793 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7794 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7797 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7798 /* compare castling rights */
7799 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7800 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7801 rights++; /* King lost rights, while rook still had them */
7802 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7803 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7804 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7805 rights++; /* but at least one rook lost them */
7807 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7808 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7810 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7811 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7812 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7815 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7816 && appData.drawRepeats > 1) {
7817 /* adjudicate after user-specified nr of repeats */
7818 int result = GameIsDrawn;
7819 char *details = "XBoard adjudication: repetition draw";
7820 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7821 // [HGM] xiangqi: check for forbidden perpetuals
7822 int m, ourPerpetual = 1, hisPerpetual = 1;
7823 for(m=forwardMostMove; m>k; m-=2) {
7824 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7825 ourPerpetual = 0; // the current mover did not always check
7826 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7827 hisPerpetual = 0; // the opponent did not always check
7829 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7830 ourPerpetual, hisPerpetual);
7831 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7832 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7833 details = "Xboard adjudication: perpetual checking";
7835 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7836 break; // (or we would have caught him before). Abort repetition-checking loop.
7838 // Now check for perpetual chases
7839 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7840 hisPerpetual = PerpetualChase(k, forwardMostMove);
7841 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7842 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7843 static char resdet[MSG_SIZ];
7844 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7846 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7848 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7849 break; // Abort repetition-checking loop.
7851 // if neither of us is checking or chasing all the time, or both are, it is draw
7853 if(engineOpponent) {
7854 SendToProgram("force\n", engineOpponent); // suppress reply
7855 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7857 GameEnds( result, details, GE_XBOARD );
7860 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7861 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7865 /* Now we test for 50-move draws. Determine ply count */
7866 count = forwardMostMove;
7867 /* look for last irreversble move */
7868 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7870 /* if we hit starting position, add initial plies */
7871 if( count == backwardMostMove )
7872 count -= initialRulePlies;
7873 count = forwardMostMove - count;
7874 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7875 // adjust reversible move counter for checks in Xiangqi
7876 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7877 if(i < backwardMostMove) i = backwardMostMove;
7878 while(i <= forwardMostMove) {
7879 lastCheck = inCheck; // check evasion does not count
7880 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7881 if(inCheck || lastCheck) count--; // check does not count
7886 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7887 /* this is used to judge if draw claims are legal */
7888 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7889 if(engineOpponent) {
7890 SendToProgram("force\n", engineOpponent); // suppress reply
7891 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7893 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7897 /* if draw offer is pending, treat it as a draw claim
7898 * when draw condition present, to allow engines a way to
7899 * claim draws before making their move to avoid a race
7900 * condition occurring after their move
7902 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7904 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7905 p = "Draw claim: 50-move rule";
7906 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7907 p = "Draw claim: 3-fold repetition";
7908 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7909 p = "Draw claim: insufficient mating material";
7910 if( p != NULL && canAdjudicate) {
7911 if(engineOpponent) {
7912 SendToProgram("force\n", engineOpponent); // suppress reply
7913 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7915 GameEnds( GameIsDrawn, p, GE_XBOARD );
7920 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7921 if(engineOpponent) {
7922 SendToProgram("force\n", engineOpponent); // suppress reply
7923 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7925 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7932 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7933 { // [HGM] book: this routine intercepts moves to simulate book replies
7934 char *bookHit = NULL;
7936 //first determine if the incoming move brings opponent into his book
7937 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7938 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7939 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7940 if(bookHit != NULL && !cps->bookSuspend) {
7941 // make sure opponent is not going to reply after receiving move to book position
7942 SendToProgram("force\n", cps);
7943 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7945 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7946 // now arrange restart after book miss
7948 // after a book hit we never send 'go', and the code after the call to this routine
7949 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7950 char buf[MSG_SIZ], *move = bookHit;
7952 int fromX, fromY, toX, toY;
7956 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7957 &fromX, &fromY, &toX, &toY, &promoChar)) {
7958 (void) CoordsToAlgebraic(boards[forwardMostMove],
7959 PosFlags(forwardMostMove),
7960 fromY, fromX, toY, toX, promoChar, move);
7962 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7966 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7967 SendToProgram(buf, cps);
7968 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7969 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7970 SendToProgram("go\n", cps);
7971 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7972 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7973 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7974 SendToProgram("go\n", cps);
7975 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7977 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7981 LoadError (char *errmess, ChessProgramState *cps)
7982 { // unloads engine and switches back to -ncp mode if it was first
7983 if(cps->initDone) return FALSE;
7984 cps->isr = NULL; // this should suppress further error popups from breaking pipes
7985 DestroyChildProcess(cps->pr, 9 ); // just to be sure
7988 appData.noChessProgram = TRUE;
7989 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7990 gameMode = BeginningOfGame; ModeHighlight();
7993 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7994 DisplayMessage("", ""); // erase waiting message
7995 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8000 ChessProgramState *savedState;
8002 DeferredBookMove (void)
8004 if(savedState->lastPing != savedState->lastPong)
8005 ScheduleDelayedEvent(DeferredBookMove, 10);
8007 HandleMachineMove(savedMessage, savedState);
8010 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8013 HandleMachineMove (char *message, ChessProgramState *cps)
8015 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8016 char realname[MSG_SIZ];
8017 int fromX, fromY, toX, toY;
8021 int machineWhite, oldError;
8024 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8025 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8026 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8027 DisplayError(_("Invalid pairing from pairing engine"), 0);
8030 pairingReceived = 1;
8032 return; // Skim the pairing messages here.
8035 oldError = cps->userError; cps->userError = 0;
8037 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8039 * Kludge to ignore BEL characters
8041 while (*message == '\007') message++;
8044 * [HGM] engine debug message: ignore lines starting with '#' character
8046 if(cps->debug && *message == '#') return;
8049 * Look for book output
8051 if (cps == &first && bookRequested) {
8052 if (message[0] == '\t' || message[0] == ' ') {
8053 /* Part of the book output is here; append it */
8054 strcat(bookOutput, message);
8055 strcat(bookOutput, " \n");
8057 } else if (bookOutput[0] != NULLCHAR) {
8058 /* All of book output has arrived; display it */
8059 char *p = bookOutput;
8060 while (*p != NULLCHAR) {
8061 if (*p == '\t') *p = ' ';
8064 DisplayInformation(bookOutput);
8065 bookRequested = FALSE;
8066 /* Fall through to parse the current output */
8071 * Look for machine move.
8073 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8074 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8076 /* This method is only useful on engines that support ping */
8077 if (cps->lastPing != cps->lastPong) {
8078 if (gameMode == BeginningOfGame) {
8079 /* Extra move from before last new; ignore */
8080 if (appData.debugMode) {
8081 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8084 if (appData.debugMode) {
8085 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8086 cps->which, gameMode);
8089 SendToProgram("undo\n", cps);
8095 case BeginningOfGame:
8096 /* Extra move from before last reset; ignore */
8097 if (appData.debugMode) {
8098 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8105 /* Extra move after we tried to stop. The mode test is
8106 not a reliable way of detecting this problem, but it's
8107 the best we can do on engines that don't support ping.
8109 if (appData.debugMode) {
8110 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8111 cps->which, gameMode);
8113 SendToProgram("undo\n", cps);
8116 case MachinePlaysWhite:
8117 case IcsPlayingWhite:
8118 machineWhite = TRUE;
8121 case MachinePlaysBlack:
8122 case IcsPlayingBlack:
8123 machineWhite = FALSE;
8126 case TwoMachinesPlay:
8127 machineWhite = (cps->twoMachinesColor[0] == 'w');
8130 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8131 if (appData.debugMode) {
8133 "Ignoring move out of turn by %s, gameMode %d"
8134 ", forwardMost %d\n",
8135 cps->which, gameMode, forwardMostMove);
8140 if(cps->alphaRank) AlphaRank(machineMove, 4);
8141 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8142 &fromX, &fromY, &toX, &toY, &promoChar)) {
8143 /* Machine move could not be parsed; ignore it. */
8144 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8145 machineMove, _(cps->which));
8146 DisplayError(buf1, 0);
8147 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8148 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8149 if (gameMode == TwoMachinesPlay) {
8150 GameEnds(machineWhite ? BlackWins : WhiteWins,
8156 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8157 /* So we have to redo legality test with true e.p. status here, */
8158 /* to make sure an illegal e.p. capture does not slip through, */
8159 /* to cause a forfeit on a justified illegal-move complaint */
8160 /* of the opponent. */
8161 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8163 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8164 fromY, fromX, toY, toX, promoChar);
8165 if(moveType == IllegalMove) {
8166 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8167 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8168 GameEnds(machineWhite ? BlackWins : WhiteWins,
8171 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8172 /* [HGM] Kludge to handle engines that send FRC-style castling
8173 when they shouldn't (like TSCP-Gothic) */
8175 case WhiteASideCastleFR:
8176 case BlackASideCastleFR:
8178 currentMoveString[2]++;
8180 case WhiteHSideCastleFR:
8181 case BlackHSideCastleFR:
8183 currentMoveString[2]--;
8185 default: ; // nothing to do, but suppresses warning of pedantic compilers
8188 hintRequested = FALSE;
8189 lastHint[0] = NULLCHAR;
8190 bookRequested = FALSE;
8191 /* Program may be pondering now */
8192 cps->maybeThinking = TRUE;
8193 if (cps->sendTime == 2) cps->sendTime = 1;
8194 if (cps->offeredDraw) cps->offeredDraw--;
8196 /* [AS] Save move info*/
8197 pvInfoList[ forwardMostMove ].score = programStats.score;
8198 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8199 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8201 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8203 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8204 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8207 while( count < adjudicateLossPlies ) {
8208 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8211 score = -score; /* Flip score for winning side */
8214 if( score > adjudicateLossThreshold ) {
8221 if( count >= adjudicateLossPlies ) {
8222 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8224 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8225 "Xboard adjudication",
8232 if(Adjudicate(cps)) {
8233 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8234 return; // [HGM] adjudicate: for all automatic game ends
8238 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8240 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8241 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8243 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8245 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8247 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8248 char buf[3*MSG_SIZ];
8250 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8251 programStats.score / 100.,
8253 programStats.time / 100.,
8254 (unsigned int)programStats.nodes,
8255 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8256 programStats.movelist);
8258 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8263 /* [AS] Clear stats for next move */
8264 ClearProgramStats();
8265 thinkOutput[0] = NULLCHAR;
8266 hiddenThinkOutputState = 0;
8269 if (gameMode == TwoMachinesPlay) {
8270 /* [HGM] relaying draw offers moved to after reception of move */
8271 /* and interpreting offer as claim if it brings draw condition */
8272 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8273 SendToProgram("draw\n", cps->other);
8275 if (cps->other->sendTime) {
8276 SendTimeRemaining(cps->other,
8277 cps->other->twoMachinesColor[0] == 'w');
8279 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8280 if (firstMove && !bookHit) {
8282 if (cps->other->useColors) {
8283 SendToProgram(cps->other->twoMachinesColor, cps->other);
8285 SendToProgram("go\n", cps->other);
8287 cps->other->maybeThinking = TRUE;
8290 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8292 if (!pausing && appData.ringBellAfterMoves) {
8297 * Reenable menu items that were disabled while
8298 * machine was thinking
8300 if (gameMode != TwoMachinesPlay)
8301 SetUserThinkingEnables();
8303 // [HGM] book: after book hit opponent has received move and is now in force mode
8304 // force the book reply into it, and then fake that it outputted this move by jumping
8305 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8307 static char bookMove[MSG_SIZ]; // a bit generous?
8309 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8310 strcat(bookMove, bookHit);
8313 programStats.nodes = programStats.depth = programStats.time =
8314 programStats.score = programStats.got_only_move = 0;
8315 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8317 if(cps->lastPing != cps->lastPong) {
8318 savedMessage = message; // args for deferred call
8320 ScheduleDelayedEvent(DeferredBookMove, 10);
8329 /* Set special modes for chess engines. Later something general
8330 * could be added here; for now there is just one kludge feature,
8331 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8332 * when "xboard" is given as an interactive command.
8334 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8335 cps->useSigint = FALSE;
8336 cps->useSigterm = FALSE;
8338 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8339 ParseFeatures(message+8, cps);
8340 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8343 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8344 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8345 int dummy, s=6; char buf[MSG_SIZ];
8346 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8347 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8348 if(startedFromSetupPosition) return;
8349 ParseFEN(boards[0], &dummy, message+s);
8350 DrawPosition(TRUE, boards[0]);
8351 startedFromSetupPosition = TRUE;
8354 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8355 * want this, I was asked to put it in, and obliged.
8357 if (!strncmp(message, "setboard ", 9)) {
8358 Board initial_position;
8360 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8362 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8363 DisplayError(_("Bad FEN received from engine"), 0);
8367 CopyBoard(boards[0], initial_position);
8368 initialRulePlies = FENrulePlies;
8369 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8370 else gameMode = MachinePlaysBlack;
8371 DrawPosition(FALSE, boards[currentMove]);
8377 * Look for communication commands
8379 if (!strncmp(message, "telluser ", 9)) {
8380 if(message[9] == '\\' && message[10] == '\\')
8381 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8383 DisplayNote(message + 9);
8386 if (!strncmp(message, "tellusererror ", 14)) {
8388 if(message[14] == '\\' && message[15] == '\\')
8389 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8391 DisplayError(message + 14, 0);
8394 if (!strncmp(message, "tellopponent ", 13)) {
8395 if (appData.icsActive) {
8397 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8401 DisplayNote(message + 13);
8405 if (!strncmp(message, "tellothers ", 11)) {
8406 if (appData.icsActive) {
8408 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8414 if (!strncmp(message, "tellall ", 8)) {
8415 if (appData.icsActive) {
8417 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8421 DisplayNote(message + 8);
8425 if (strncmp(message, "warning", 7) == 0) {
8426 /* Undocumented feature, use tellusererror in new code */
8427 DisplayError(message, 0);
8430 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8431 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8432 strcat(realname, " query");
8433 AskQuestion(realname, buf2, buf1, cps->pr);
8436 /* Commands from the engine directly to ICS. We don't allow these to be
8437 * sent until we are logged on. Crafty kibitzes have been known to
8438 * interfere with the login process.
8441 if (!strncmp(message, "tellics ", 8)) {
8442 SendToICS(message + 8);
8446 if (!strncmp(message, "tellicsnoalias ", 15)) {
8447 SendToICS(ics_prefix);
8448 SendToICS(message + 15);
8452 /* The following are for backward compatibility only */
8453 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8454 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8455 SendToICS(ics_prefix);
8461 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8465 * If the move is illegal, cancel it and redraw the board.
8466 * Also deal with other error cases. Matching is rather loose
8467 * here to accommodate engines written before the spec.
8469 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8470 strncmp(message, "Error", 5) == 0) {
8471 if (StrStr(message, "name") ||
8472 StrStr(message, "rating") || StrStr(message, "?") ||
8473 StrStr(message, "result") || StrStr(message, "board") ||
8474 StrStr(message, "bk") || StrStr(message, "computer") ||
8475 StrStr(message, "variant") || StrStr(message, "hint") ||
8476 StrStr(message, "random") || StrStr(message, "depth") ||
8477 StrStr(message, "accepted")) {
8480 if (StrStr(message, "protover")) {
8481 /* Program is responding to input, so it's apparently done
8482 initializing, and this error message indicates it is
8483 protocol version 1. So we don't need to wait any longer
8484 for it to initialize and send feature commands. */
8485 FeatureDone(cps, 1);
8486 cps->protocolVersion = 1;
8489 cps->maybeThinking = FALSE;
8491 if (StrStr(message, "draw")) {
8492 /* Program doesn't have "draw" command */
8493 cps->sendDrawOffers = 0;
8496 if (cps->sendTime != 1 &&
8497 (StrStr(message, "time") || StrStr(message, "otim"))) {
8498 /* Program apparently doesn't have "time" or "otim" command */
8502 if (StrStr(message, "analyze")) {
8503 cps->analysisSupport = FALSE;
8504 cps->analyzing = FALSE;
8505 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8506 EditGameEvent(); // [HGM] try to preserve loaded game
8507 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8508 DisplayError(buf2, 0);
8511 if (StrStr(message, "(no matching move)st")) {
8512 /* Special kludge for GNU Chess 4 only */
8513 cps->stKludge = TRUE;
8514 SendTimeControl(cps, movesPerSession, timeControl,
8515 timeIncrement, appData.searchDepth,
8519 if (StrStr(message, "(no matching move)sd")) {
8520 /* Special kludge for GNU Chess 4 only */
8521 cps->sdKludge = TRUE;
8522 SendTimeControl(cps, movesPerSession, timeControl,
8523 timeIncrement, appData.searchDepth,
8527 if (!StrStr(message, "llegal")) {
8530 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8531 gameMode == IcsIdle) return;
8532 if (forwardMostMove <= backwardMostMove) return;
8533 if (pausing) PauseEvent();
8534 if(appData.forceIllegal) {
8535 // [HGM] illegal: machine refused move; force position after move into it
8536 SendToProgram("force\n", cps);
8537 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8538 // we have a real problem now, as SendBoard will use the a2a3 kludge
8539 // when black is to move, while there might be nothing on a2 or black
8540 // might already have the move. So send the board as if white has the move.
8541 // But first we must change the stm of the engine, as it refused the last move
8542 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8543 if(WhiteOnMove(forwardMostMove)) {
8544 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8545 SendBoard(cps, forwardMostMove); // kludgeless board
8547 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8548 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8549 SendBoard(cps, forwardMostMove+1); // kludgeless board
8551 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8552 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8553 gameMode == TwoMachinesPlay)
8554 SendToProgram("go\n", cps);
8557 if (gameMode == PlayFromGameFile) {
8558 /* Stop reading this game file */
8559 gameMode = EditGame;
8562 /* [HGM] illegal-move claim should forfeit game when Xboard */
8563 /* only passes fully legal moves */
8564 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8565 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8566 "False illegal-move claim", GE_XBOARD );
8567 return; // do not take back move we tested as valid
8569 currentMove = forwardMostMove-1;
8570 DisplayMove(currentMove-1); /* before DisplayMoveError */
8571 SwitchClocks(forwardMostMove-1); // [HGM] race
8572 DisplayBothClocks();
8573 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8574 parseList[currentMove], _(cps->which));
8575 DisplayMoveError(buf1);
8576 DrawPosition(FALSE, boards[currentMove]);
8578 SetUserThinkingEnables();
8581 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8582 /* Program has a broken "time" command that
8583 outputs a string not ending in newline.
8589 * If chess program startup fails, exit with an error message.
8590 * Attempts to recover here are futile. [HGM] Well, we try anyway
8592 if ((StrStr(message, "unknown host") != NULL)
8593 || (StrStr(message, "No remote directory") != NULL)
8594 || (StrStr(message, "not found") != NULL)
8595 || (StrStr(message, "No such file") != NULL)
8596 || (StrStr(message, "can't alloc") != NULL)
8597 || (StrStr(message, "Permission denied") != NULL)) {
8599 cps->maybeThinking = FALSE;
8600 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8601 _(cps->which), cps->program, cps->host, message);
8602 RemoveInputSource(cps->isr);
8603 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8604 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8605 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8611 * Look for hint output
8613 if (sscanf(message, "Hint: %s", buf1) == 1) {
8614 if (cps == &first && hintRequested) {
8615 hintRequested = FALSE;
8616 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8617 &fromX, &fromY, &toX, &toY, &promoChar)) {
8618 (void) CoordsToAlgebraic(boards[forwardMostMove],
8619 PosFlags(forwardMostMove),
8620 fromY, fromX, toY, toX, promoChar, buf1);
8621 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8622 DisplayInformation(buf2);
8624 /* Hint move could not be parsed!? */
8625 snprintf(buf2, sizeof(buf2),
8626 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8627 buf1, _(cps->which));
8628 DisplayError(buf2, 0);
8631 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8637 * Ignore other messages if game is not in progress
8639 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8640 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8643 * look for win, lose, draw, or draw offer
8645 if (strncmp(message, "1-0", 3) == 0) {
8646 char *p, *q, *r = "";
8647 p = strchr(message, '{');
8655 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8657 } else if (strncmp(message, "0-1", 3) == 0) {
8658 char *p, *q, *r = "";
8659 p = strchr(message, '{');
8667 /* Kludge for Arasan 4.1 bug */
8668 if (strcmp(r, "Black resigns") == 0) {
8669 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8672 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8674 } else if (strncmp(message, "1/2", 3) == 0) {
8675 char *p, *q, *r = "";
8676 p = strchr(message, '{');
8685 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8688 } else if (strncmp(message, "White resign", 12) == 0) {
8689 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8691 } else if (strncmp(message, "Black resign", 12) == 0) {
8692 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8694 } else if (strncmp(message, "White matches", 13) == 0 ||
8695 strncmp(message, "Black matches", 13) == 0 ) {
8696 /* [HGM] ignore GNUShogi noises */
8698 } else if (strncmp(message, "White", 5) == 0 &&
8699 message[5] != '(' &&
8700 StrStr(message, "Black") == NULL) {
8701 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8703 } else if (strncmp(message, "Black", 5) == 0 &&
8704 message[5] != '(') {
8705 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8707 } else if (strcmp(message, "resign") == 0 ||
8708 strcmp(message, "computer resigns") == 0) {
8710 case MachinePlaysBlack:
8711 case IcsPlayingBlack:
8712 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8714 case MachinePlaysWhite:
8715 case IcsPlayingWhite:
8716 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8718 case TwoMachinesPlay:
8719 if (cps->twoMachinesColor[0] == 'w')
8720 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8722 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8729 } else if (strncmp(message, "opponent mates", 14) == 0) {
8731 case MachinePlaysBlack:
8732 case IcsPlayingBlack:
8733 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8735 case MachinePlaysWhite:
8736 case IcsPlayingWhite:
8737 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8739 case TwoMachinesPlay:
8740 if (cps->twoMachinesColor[0] == 'w')
8741 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8743 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8750 } else if (strncmp(message, "computer mates", 14) == 0) {
8752 case MachinePlaysBlack:
8753 case IcsPlayingBlack:
8754 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8756 case MachinePlaysWhite:
8757 case IcsPlayingWhite:
8758 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8760 case TwoMachinesPlay:
8761 if (cps->twoMachinesColor[0] == 'w')
8762 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8764 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8771 } else if (strncmp(message, "checkmate", 9) == 0) {
8772 if (WhiteOnMove(forwardMostMove)) {
8773 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8775 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8778 } else if (strstr(message, "Draw") != NULL ||
8779 strstr(message, "game is a draw") != NULL) {
8780 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8782 } else if (strstr(message, "offer") != NULL &&
8783 strstr(message, "draw") != NULL) {
8785 if (appData.zippyPlay && first.initDone) {
8786 /* Relay offer to ICS */
8787 SendToICS(ics_prefix);
8788 SendToICS("draw\n");
8791 cps->offeredDraw = 2; /* valid until this engine moves twice */
8792 if (gameMode == TwoMachinesPlay) {
8793 if (cps->other->offeredDraw) {
8794 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8795 /* [HGM] in two-machine mode we delay relaying draw offer */
8796 /* until after we also have move, to see if it is really claim */
8798 } else if (gameMode == MachinePlaysWhite ||
8799 gameMode == MachinePlaysBlack) {
8800 if (userOfferedDraw) {
8801 DisplayInformation(_("Machine accepts your draw offer"));
8802 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8804 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8811 * Look for thinking output
8813 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8814 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8816 int plylev, mvleft, mvtot, curscore, time;
8817 char mvname[MOVE_LEN];
8821 int prefixHint = FALSE;
8822 mvname[0] = NULLCHAR;
8825 case MachinePlaysBlack:
8826 case IcsPlayingBlack:
8827 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8829 case MachinePlaysWhite:
8830 case IcsPlayingWhite:
8831 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8836 case IcsObserving: /* [DM] icsEngineAnalyze */
8837 if (!appData.icsEngineAnalyze) ignore = TRUE;
8839 case TwoMachinesPlay:
8840 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8850 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8852 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8853 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8855 if (plyext != ' ' && plyext != '\t') {
8859 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8860 if( cps->scoreIsAbsolute &&
8861 ( gameMode == MachinePlaysBlack ||
8862 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8863 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8864 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8865 !WhiteOnMove(currentMove)
8868 curscore = -curscore;
8871 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8873 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8876 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8877 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8878 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8879 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8880 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8881 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8883 } else DisplayError(_("failed writing PV"), 0);
8886 tempStats.depth = plylev;
8887 tempStats.nodes = nodes;
8888 tempStats.time = time;
8889 tempStats.score = curscore;
8890 tempStats.got_only_move = 0;
8892 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8895 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8896 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8897 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8898 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8899 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8900 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8901 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8902 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8905 /* Buffer overflow protection */
8906 if (pv[0] != NULLCHAR) {
8907 if (strlen(pv) >= sizeof(tempStats.movelist)
8908 && appData.debugMode) {
8910 "PV is too long; using the first %u bytes.\n",
8911 (unsigned) sizeof(tempStats.movelist) - 1);
8914 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8916 sprintf(tempStats.movelist, " no PV\n");
8919 if (tempStats.seen_stat) {
8920 tempStats.ok_to_send = 1;
8923 if (strchr(tempStats.movelist, '(') != NULL) {
8924 tempStats.line_is_book = 1;
8925 tempStats.nr_moves = 0;
8926 tempStats.moves_left = 0;
8928 tempStats.line_is_book = 0;
8931 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8932 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8934 SendProgramStatsToFrontend( cps, &tempStats );
8937 [AS] Protect the thinkOutput buffer from overflow... this
8938 is only useful if buf1 hasn't overflowed first!
8940 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8942 (gameMode == TwoMachinesPlay ?
8943 ToUpper(cps->twoMachinesColor[0]) : ' '),
8944 ((double) curscore) / 100.0,
8945 prefixHint ? lastHint : "",
8946 prefixHint ? " " : "" );
8948 if( buf1[0] != NULLCHAR ) {
8949 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8951 if( strlen(pv) > max_len ) {
8952 if( appData.debugMode) {
8953 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8955 pv[max_len+1] = '\0';
8958 strcat( thinkOutput, pv);
8961 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8962 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8963 DisplayMove(currentMove - 1);
8967 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8968 /* crafty (9.25+) says "(only move) <move>"
8969 * if there is only 1 legal move
8971 sscanf(p, "(only move) %s", buf1);
8972 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8973 sprintf(programStats.movelist, "%s (only move)", buf1);
8974 programStats.depth = 1;
8975 programStats.nr_moves = 1;
8976 programStats.moves_left = 1;
8977 programStats.nodes = 1;
8978 programStats.time = 1;
8979 programStats.got_only_move = 1;
8981 /* Not really, but we also use this member to
8982 mean "line isn't going to change" (Crafty
8983 isn't searching, so stats won't change) */
8984 programStats.line_is_book = 1;
8986 SendProgramStatsToFrontend( cps, &programStats );
8988 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8989 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8990 DisplayMove(currentMove - 1);
8993 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8994 &time, &nodes, &plylev, &mvleft,
8995 &mvtot, mvname) >= 5) {
8996 /* The stat01: line is from Crafty (9.29+) in response
8997 to the "." command */
8998 programStats.seen_stat = 1;
8999 cps->maybeThinking = TRUE;
9001 if (programStats.got_only_move || !appData.periodicUpdates)
9004 programStats.depth = plylev;
9005 programStats.time = time;
9006 programStats.nodes = nodes;
9007 programStats.moves_left = mvleft;
9008 programStats.nr_moves = mvtot;
9009 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9010 programStats.ok_to_send = 1;
9011 programStats.movelist[0] = '\0';
9013 SendProgramStatsToFrontend( cps, &programStats );
9017 } else if (strncmp(message,"++",2) == 0) {
9018 /* Crafty 9.29+ outputs this */
9019 programStats.got_fail = 2;
9022 } else if (strncmp(message,"--",2) == 0) {
9023 /* Crafty 9.29+ outputs this */
9024 programStats.got_fail = 1;
9027 } else if (thinkOutput[0] != NULLCHAR &&
9028 strncmp(message, " ", 4) == 0) {
9029 unsigned message_len;
9032 while (*p && *p == ' ') p++;
9034 message_len = strlen( p );
9036 /* [AS] Avoid buffer overflow */
9037 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9038 strcat(thinkOutput, " ");
9039 strcat(thinkOutput, p);
9042 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9043 strcat(programStats.movelist, " ");
9044 strcat(programStats.movelist, p);
9047 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9048 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9049 DisplayMove(currentMove - 1);
9057 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9058 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9060 ChessProgramStats cpstats;
9062 if (plyext != ' ' && plyext != '\t') {
9066 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9067 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9068 curscore = -curscore;
9071 cpstats.depth = plylev;
9072 cpstats.nodes = nodes;
9073 cpstats.time = time;
9074 cpstats.score = curscore;
9075 cpstats.got_only_move = 0;
9076 cpstats.movelist[0] = '\0';
9078 if (buf1[0] != NULLCHAR) {
9079 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9082 cpstats.ok_to_send = 0;
9083 cpstats.line_is_book = 0;
9084 cpstats.nr_moves = 0;
9085 cpstats.moves_left = 0;
9087 SendProgramStatsToFrontend( cps, &cpstats );
9094 /* Parse a game score from the character string "game", and
9095 record it as the history of the current game. The game
9096 score is NOT assumed to start from the standard position.
9097 The display is not updated in any way.
9100 ParseGameHistory (char *game)
9103 int fromX, fromY, toX, toY, boardIndex;
9108 if (appData.debugMode)
9109 fprintf(debugFP, "Parsing game history: %s\n", game);
9111 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9112 gameInfo.site = StrSave(appData.icsHost);
9113 gameInfo.date = PGNDate();
9114 gameInfo.round = StrSave("-");
9116 /* Parse out names of players */
9117 while (*game == ' ') game++;
9119 while (*game != ' ') *p++ = *game++;
9121 gameInfo.white = StrSave(buf);
9122 while (*game == ' ') game++;
9124 while (*game != ' ' && *game != '\n') *p++ = *game++;
9126 gameInfo.black = StrSave(buf);
9129 boardIndex = blackPlaysFirst ? 1 : 0;
9132 yyboardindex = boardIndex;
9133 moveType = (ChessMove) Myylex();
9135 case IllegalMove: /* maybe suicide chess, etc. */
9136 if (appData.debugMode) {
9137 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9138 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9139 setbuf(debugFP, NULL);
9141 case WhitePromotion:
9142 case BlackPromotion:
9143 case WhiteNonPromotion:
9144 case BlackNonPromotion:
9146 case WhiteCapturesEnPassant:
9147 case BlackCapturesEnPassant:
9148 case WhiteKingSideCastle:
9149 case WhiteQueenSideCastle:
9150 case BlackKingSideCastle:
9151 case BlackQueenSideCastle:
9152 case WhiteKingSideCastleWild:
9153 case WhiteQueenSideCastleWild:
9154 case BlackKingSideCastleWild:
9155 case BlackQueenSideCastleWild:
9157 case WhiteHSideCastleFR:
9158 case WhiteASideCastleFR:
9159 case BlackHSideCastleFR:
9160 case BlackASideCastleFR:
9162 fromX = currentMoveString[0] - AAA;
9163 fromY = currentMoveString[1] - ONE;
9164 toX = currentMoveString[2] - AAA;
9165 toY = currentMoveString[3] - ONE;
9166 promoChar = currentMoveString[4];
9170 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9171 fromX = moveType == WhiteDrop ?
9172 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9173 (int) CharToPiece(ToLower(currentMoveString[0]));
9175 toX = currentMoveString[2] - AAA;
9176 toY = currentMoveString[3] - ONE;
9177 promoChar = NULLCHAR;
9181 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9182 if (appData.debugMode) {
9183 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9184 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9185 setbuf(debugFP, NULL);
9187 DisplayError(buf, 0);
9189 case ImpossibleMove:
9191 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9192 if (appData.debugMode) {
9193 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9194 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9195 setbuf(debugFP, NULL);
9197 DisplayError(buf, 0);
9200 if (boardIndex < backwardMostMove) {
9201 /* Oops, gap. How did that happen? */
9202 DisplayError(_("Gap in move list"), 0);
9205 backwardMostMove = blackPlaysFirst ? 1 : 0;
9206 if (boardIndex > forwardMostMove) {
9207 forwardMostMove = boardIndex;
9211 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9212 strcat(parseList[boardIndex-1], " ");
9213 strcat(parseList[boardIndex-1], yy_text);
9225 case GameUnfinished:
9226 if (gameMode == IcsExamining) {
9227 if (boardIndex < backwardMostMove) {
9228 /* Oops, gap. How did that happen? */
9231 backwardMostMove = blackPlaysFirst ? 1 : 0;
9234 gameInfo.result = moveType;
9235 p = strchr(yy_text, '{');
9236 if (p == NULL) p = strchr(yy_text, '(');
9239 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9241 q = strchr(p, *p == '{' ? '}' : ')');
9242 if (q != NULL) *q = NULLCHAR;
9245 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9246 gameInfo.resultDetails = StrSave(p);
9249 if (boardIndex >= forwardMostMove &&
9250 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9251 backwardMostMove = blackPlaysFirst ? 1 : 0;
9254 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9255 fromY, fromX, toY, toX, promoChar,
9256 parseList[boardIndex]);
9257 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9258 /* currentMoveString is set as a side-effect of yylex */
9259 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9260 strcat(moveList[boardIndex], "\n");
9262 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9263 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9269 if(gameInfo.variant != VariantShogi)
9270 strcat(parseList[boardIndex - 1], "+");
9274 strcat(parseList[boardIndex - 1], "#");
9281 /* Apply a move to the given board */
9283 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9285 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9286 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9288 /* [HGM] compute & store e.p. status and castling rights for new position */
9289 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9291 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9292 oldEP = (signed char)board[EP_STATUS];
9293 board[EP_STATUS] = EP_NONE;
9295 if (fromY == DROP_RANK) {
9297 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9298 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9301 piece = board[toY][toX] = (ChessSquare) fromX;
9305 if( board[toY][toX] != EmptySquare )
9306 board[EP_STATUS] = EP_CAPTURE;
9308 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9309 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9310 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9312 if( board[fromY][fromX] == WhitePawn ) {
9313 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9314 board[EP_STATUS] = EP_PAWN_MOVE;
9316 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9317 gameInfo.variant != VariantBerolina || toX < fromX)
9318 board[EP_STATUS] = toX | berolina;
9319 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9320 gameInfo.variant != VariantBerolina || toX > fromX)
9321 board[EP_STATUS] = toX;
9324 if( board[fromY][fromX] == BlackPawn ) {
9325 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9326 board[EP_STATUS] = EP_PAWN_MOVE;
9327 if( toY-fromY== -2) {
9328 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9329 gameInfo.variant != VariantBerolina || toX < fromX)
9330 board[EP_STATUS] = toX | berolina;
9331 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9332 gameInfo.variant != VariantBerolina || toX > fromX)
9333 board[EP_STATUS] = toX;
9337 for(i=0; i<nrCastlingRights; i++) {
9338 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9339 board[CASTLING][i] == toX && castlingRank[i] == toY
9340 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9343 if(gameInfo.variant == VariantSChess) { // update virginity
9344 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9345 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9346 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9347 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9350 if (fromX == toX && fromY == toY) return;
9352 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9353 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9354 if(gameInfo.variant == VariantKnightmate)
9355 king += (int) WhiteUnicorn - (int) WhiteKing;
9357 /* Code added by Tord: */
9358 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9359 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9360 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9361 board[fromY][fromX] = EmptySquare;
9362 board[toY][toX] = EmptySquare;
9363 if((toX > fromX) != (piece == WhiteRook)) {
9364 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9366 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9368 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9369 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9370 board[fromY][fromX] = EmptySquare;
9371 board[toY][toX] = EmptySquare;
9372 if((toX > fromX) != (piece == BlackRook)) {
9373 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9375 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9377 /* End of code added by Tord */
9379 } else if (board[fromY][fromX] == king
9380 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9381 && toY == fromY && toX > fromX+1) {
9382 board[fromY][fromX] = EmptySquare;
9383 board[toY][toX] = king;
9384 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9385 board[fromY][BOARD_RGHT-1] = EmptySquare;
9386 } else if (board[fromY][fromX] == king
9387 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9388 && toY == fromY && toX < fromX-1) {
9389 board[fromY][fromX] = EmptySquare;
9390 board[toY][toX] = king;
9391 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9392 board[fromY][BOARD_LEFT] = EmptySquare;
9393 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9394 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9395 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9397 /* white pawn promotion */
9398 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9399 if(gameInfo.variant==VariantBughouse ||
9400 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9401 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9402 board[fromY][fromX] = EmptySquare;
9403 } else if ((fromY >= BOARD_HEIGHT>>1)
9405 && gameInfo.variant != VariantXiangqi
9406 && gameInfo.variant != VariantBerolina
9407 && (board[fromY][fromX] == WhitePawn)
9408 && (board[toY][toX] == EmptySquare)) {
9409 board[fromY][fromX] = EmptySquare;
9410 board[toY][toX] = WhitePawn;
9411 captured = board[toY - 1][toX];
9412 board[toY - 1][toX] = EmptySquare;
9413 } else if ((fromY == BOARD_HEIGHT-4)
9415 && gameInfo.variant == VariantBerolina
9416 && (board[fromY][fromX] == WhitePawn)
9417 && (board[toY][toX] == EmptySquare)) {
9418 board[fromY][fromX] = EmptySquare;
9419 board[toY][toX] = WhitePawn;
9420 if(oldEP & EP_BEROLIN_A) {
9421 captured = board[fromY][fromX-1];
9422 board[fromY][fromX-1] = EmptySquare;
9423 }else{ captured = board[fromY][fromX+1];
9424 board[fromY][fromX+1] = EmptySquare;
9426 } else if (board[fromY][fromX] == king
9427 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9428 && toY == fromY && toX > fromX+1) {
9429 board[fromY][fromX] = EmptySquare;
9430 board[toY][toX] = king;
9431 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9432 board[fromY][BOARD_RGHT-1] = EmptySquare;
9433 } else if (board[fromY][fromX] == king
9434 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9435 && toY == fromY && toX < fromX-1) {
9436 board[fromY][fromX] = EmptySquare;
9437 board[toY][toX] = king;
9438 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9439 board[fromY][BOARD_LEFT] = EmptySquare;
9440 } else if (fromY == 7 && fromX == 3
9441 && board[fromY][fromX] == BlackKing
9442 && toY == 7 && toX == 5) {
9443 board[fromY][fromX] = EmptySquare;
9444 board[toY][toX] = BlackKing;
9445 board[fromY][7] = EmptySquare;
9446 board[toY][4] = BlackRook;
9447 } else if (fromY == 7 && fromX == 3
9448 && board[fromY][fromX] == BlackKing
9449 && toY == 7 && toX == 1) {
9450 board[fromY][fromX] = EmptySquare;
9451 board[toY][toX] = BlackKing;
9452 board[fromY][0] = EmptySquare;
9453 board[toY][2] = BlackRook;
9454 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9455 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9456 && toY < promoRank && promoChar
9458 /* black pawn promotion */
9459 board[toY][toX] = CharToPiece(ToLower(promoChar));
9460 if(gameInfo.variant==VariantBughouse ||
9461 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9462 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9463 board[fromY][fromX] = EmptySquare;
9464 } else if ((fromY < BOARD_HEIGHT>>1)
9466 && gameInfo.variant != VariantXiangqi
9467 && gameInfo.variant != VariantBerolina
9468 && (board[fromY][fromX] == BlackPawn)
9469 && (board[toY][toX] == EmptySquare)) {
9470 board[fromY][fromX] = EmptySquare;
9471 board[toY][toX] = BlackPawn;
9472 captured = board[toY + 1][toX];
9473 board[toY + 1][toX] = EmptySquare;
9474 } else if ((fromY == 3)
9476 && gameInfo.variant == VariantBerolina
9477 && (board[fromY][fromX] == BlackPawn)
9478 && (board[toY][toX] == EmptySquare)) {
9479 board[fromY][fromX] = EmptySquare;
9480 board[toY][toX] = BlackPawn;
9481 if(oldEP & EP_BEROLIN_A) {
9482 captured = board[fromY][fromX-1];
9483 board[fromY][fromX-1] = EmptySquare;
9484 }else{ captured = board[fromY][fromX+1];
9485 board[fromY][fromX+1] = EmptySquare;
9488 board[toY][toX] = board[fromY][fromX];
9489 board[fromY][fromX] = EmptySquare;
9493 if (gameInfo.holdingsWidth != 0) {
9495 /* !!A lot more code needs to be written to support holdings */
9496 /* [HGM] OK, so I have written it. Holdings are stored in the */
9497 /* penultimate board files, so they are automaticlly stored */
9498 /* in the game history. */
9499 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9500 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9501 /* Delete from holdings, by decreasing count */
9502 /* and erasing image if necessary */
9503 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9504 if(p < (int) BlackPawn) { /* white drop */
9505 p -= (int)WhitePawn;
9506 p = PieceToNumber((ChessSquare)p);
9507 if(p >= gameInfo.holdingsSize) p = 0;
9508 if(--board[p][BOARD_WIDTH-2] <= 0)
9509 board[p][BOARD_WIDTH-1] = EmptySquare;
9510 if((int)board[p][BOARD_WIDTH-2] < 0)
9511 board[p][BOARD_WIDTH-2] = 0;
9512 } else { /* black drop */
9513 p -= (int)BlackPawn;
9514 p = PieceToNumber((ChessSquare)p);
9515 if(p >= gameInfo.holdingsSize) p = 0;
9516 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9517 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9518 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9519 board[BOARD_HEIGHT-1-p][1] = 0;
9522 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9523 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9524 /* [HGM] holdings: Add to holdings, if holdings exist */
9525 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9526 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9527 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9530 if (p >= (int) BlackPawn) {
9531 p -= (int)BlackPawn;
9532 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9533 /* in Shogi restore piece to its original first */
9534 captured = (ChessSquare) (DEMOTED captured);
9537 p = PieceToNumber((ChessSquare)p);
9538 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9539 board[p][BOARD_WIDTH-2]++;
9540 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9542 p -= (int)WhitePawn;
9543 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9544 captured = (ChessSquare) (DEMOTED captured);
9547 p = PieceToNumber((ChessSquare)p);
9548 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9549 board[BOARD_HEIGHT-1-p][1]++;
9550 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9553 } else if (gameInfo.variant == VariantAtomic) {
9554 if (captured != EmptySquare) {
9556 for (y = toY-1; y <= toY+1; y++) {
9557 for (x = toX-1; x <= toX+1; x++) {
9558 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9559 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9560 board[y][x] = EmptySquare;
9564 board[toY][toX] = EmptySquare;
9567 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9568 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9570 if(promoChar == '+') {
9571 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9572 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9573 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9574 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9575 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9576 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9577 board[toY][toX] = newPiece;
9579 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9580 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9581 // [HGM] superchess: take promotion piece out of holdings
9582 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9583 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9584 if(!--board[k][BOARD_WIDTH-2])
9585 board[k][BOARD_WIDTH-1] = EmptySquare;
9587 if(!--board[BOARD_HEIGHT-1-k][1])
9588 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9594 /* Updates forwardMostMove */
9596 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9598 // forwardMostMove++; // [HGM] bare: moved downstream
9600 (void) CoordsToAlgebraic(boards[forwardMostMove],
9601 PosFlags(forwardMostMove),
9602 fromY, fromX, toY, toX, promoChar,
9603 parseList[forwardMostMove]);
9605 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9606 int timeLeft; static int lastLoadFlag=0; int king, piece;
9607 piece = boards[forwardMostMove][fromY][fromX];
9608 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9609 if(gameInfo.variant == VariantKnightmate)
9610 king += (int) WhiteUnicorn - (int) WhiteKing;
9611 if(forwardMostMove == 0) {
9612 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9613 fprintf(serverMoves, "%s;", UserName());
9614 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9615 fprintf(serverMoves, "%s;", second.tidy);
9616 fprintf(serverMoves, "%s;", first.tidy);
9617 if(gameMode == MachinePlaysWhite)
9618 fprintf(serverMoves, "%s;", UserName());
9619 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9620 fprintf(serverMoves, "%s;", second.tidy);
9621 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9622 lastLoadFlag = loadFlag;
9624 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9625 // print castling suffix
9626 if( toY == fromY && piece == king ) {
9628 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9630 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9633 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9634 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9635 boards[forwardMostMove][toY][toX] == EmptySquare
9636 && fromX != toX && fromY != toY)
9637 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9639 if(promoChar != NULLCHAR) {
9640 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9641 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9642 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9643 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9646 char buf[MOVE_LEN*2], *p; int len;
9647 fprintf(serverMoves, "/%d/%d",
9648 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9649 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9650 else timeLeft = blackTimeRemaining/1000;
9651 fprintf(serverMoves, "/%d", timeLeft);
9652 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9653 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9654 if(p = strchr(buf, '=')) *p = NULLCHAR;
9655 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9656 fprintf(serverMoves, "/%s", buf);
9658 fflush(serverMoves);
9661 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9662 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9665 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9666 if (commentList[forwardMostMove+1] != NULL) {
9667 free(commentList[forwardMostMove+1]);
9668 commentList[forwardMostMove+1] = NULL;
9670 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9671 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9672 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9673 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9674 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9675 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9676 adjustedClock = FALSE;
9677 gameInfo.result = GameUnfinished;
9678 if (gameInfo.resultDetails != NULL) {
9679 free(gameInfo.resultDetails);
9680 gameInfo.resultDetails = NULL;
9682 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9683 moveList[forwardMostMove - 1]);
9684 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9690 if(gameInfo.variant != VariantShogi)
9691 strcat(parseList[forwardMostMove - 1], "+");
9695 strcat(parseList[forwardMostMove - 1], "#");
9701 /* Updates currentMove if not pausing */
9703 ShowMove (int fromX, int fromY, int toX, int toY)
9705 int instant = (gameMode == PlayFromGameFile) ?
9706 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9707 if(appData.noGUI) return;
9708 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9710 if (forwardMostMove == currentMove + 1) {
9711 AnimateMove(boards[forwardMostMove - 1],
9712 fromX, fromY, toX, toY);
9714 if (appData.highlightLastMove) {
9715 SetHighlights(fromX, fromY, toX, toY);
9718 currentMove = forwardMostMove;
9721 if (instant) return;
9723 DisplayMove(currentMove - 1);
9724 DrawPosition(FALSE, boards[currentMove]);
9725 DisplayBothClocks();
9726 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9730 SendEgtPath (ChessProgramState *cps)
9731 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9732 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9734 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9737 char c, *q = name+1, *r, *s;
9739 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9740 while(*p && *p != ',') *q++ = *p++;
9742 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9743 strcmp(name, ",nalimov:") == 0 ) {
9744 // take nalimov path from the menu-changeable option first, if it is defined
9745 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9746 SendToProgram(buf,cps); // send egtbpath command for nalimov
9748 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9749 (s = StrStr(appData.egtFormats, name)) != NULL) {
9750 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9751 s = r = StrStr(s, ":") + 1; // beginning of path info
9752 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9753 c = *r; *r = 0; // temporarily null-terminate path info
9754 *--q = 0; // strip of trailig ':' from name
9755 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9757 SendToProgram(buf,cps); // send egtbpath command for this format
9759 if(*p == ',') p++; // read away comma to position for next format name
9764 InitChessProgram (ChessProgramState *cps, int setup)
9765 /* setup needed to setup FRC opening position */
9767 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9768 if (appData.noChessProgram) return;
9769 hintRequested = FALSE;
9770 bookRequested = FALSE;
9772 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9773 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9774 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9775 if(cps->memSize) { /* [HGM] memory */
9776 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9777 SendToProgram(buf, cps);
9779 SendEgtPath(cps); /* [HGM] EGT */
9780 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9781 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9782 SendToProgram(buf, cps);
9785 SendToProgram(cps->initString, cps);
9786 if (gameInfo.variant != VariantNormal &&
9787 gameInfo.variant != VariantLoadable
9788 /* [HGM] also send variant if board size non-standard */
9789 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9791 char *v = VariantName(gameInfo.variant);
9792 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9793 /* [HGM] in protocol 1 we have to assume all variants valid */
9794 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9795 DisplayFatalError(buf, 0, 1);
9799 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9800 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9801 if( gameInfo.variant == VariantXiangqi )
9802 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9803 if( gameInfo.variant == VariantShogi )
9804 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9805 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9806 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9807 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9808 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9809 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9810 if( gameInfo.variant == VariantCourier )
9811 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9812 if( gameInfo.variant == VariantSuper )
9813 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9814 if( gameInfo.variant == VariantGreat )
9815 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9816 if( gameInfo.variant == VariantSChess )
9817 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9818 if( gameInfo.variant == VariantGrand )
9819 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9822 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9823 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9824 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9825 if(StrStr(cps->variants, b) == NULL) {
9826 // specific sized variant not known, check if general sizing allowed
9827 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9828 if(StrStr(cps->variants, "boardsize") == NULL) {
9829 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9830 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9831 DisplayFatalError(buf, 0, 1);
9834 /* [HGM] here we really should compare with the maximum supported board size */
9837 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9838 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9839 SendToProgram(buf, cps);
9841 currentlyInitializedVariant = gameInfo.variant;
9843 /* [HGM] send opening position in FRC to first engine */
9845 SendToProgram("force\n", cps);
9847 /* engine is now in force mode! Set flag to wake it up after first move. */
9848 setboardSpoiledMachineBlack = 1;
9852 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9853 SendToProgram(buf, cps);
9855 cps->maybeThinking = FALSE;
9856 cps->offeredDraw = 0;
9857 if (!appData.icsActive) {
9858 SendTimeControl(cps, movesPerSession, timeControl,
9859 timeIncrement, appData.searchDepth,
9862 if (appData.showThinking
9863 // [HGM] thinking: four options require thinking output to be sent
9864 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9866 SendToProgram("post\n", cps);
9868 SendToProgram("hard\n", cps);
9869 if (!appData.ponderNextMove) {
9870 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9871 it without being sure what state we are in first. "hard"
9872 is not a toggle, so that one is OK.
9874 SendToProgram("easy\n", cps);
9877 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9878 SendToProgram(buf, cps);
9880 cps->initDone = TRUE;
9881 ClearEngineOutputPane(cps == &second);
9886 StartChessProgram (ChessProgramState *cps)
9891 if (appData.noChessProgram) return;
9892 cps->initDone = FALSE;
9894 if (strcmp(cps->host, "localhost") == 0) {
9895 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9896 } else if (*appData.remoteShell == NULLCHAR) {
9897 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9899 if (*appData.remoteUser == NULLCHAR) {
9900 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9903 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9904 cps->host, appData.remoteUser, cps->program);
9906 err = StartChildProcess(buf, "", &cps->pr);
9910 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9911 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9912 if(cps != &first) return;
9913 appData.noChessProgram = TRUE;
9916 // DisplayFatalError(buf, err, 1);
9917 // cps->pr = NoProc;
9922 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9923 if (cps->protocolVersion > 1) {
9924 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9925 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9926 cps->comboCnt = 0; // and values of combo boxes
9927 SendToProgram(buf, cps);
9929 SendToProgram("xboard\n", cps);
9934 TwoMachinesEventIfReady P((void))
9936 static int curMess = 0;
9937 if (first.lastPing != first.lastPong) {
9938 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9939 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9942 if (second.lastPing != second.lastPong) {
9943 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9944 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9947 DisplayMessage("", ""); curMess = 0;
9953 MakeName (char *template)
9957 static char buf[MSG_SIZ];
9961 clock = time((time_t *)NULL);
9962 tm = localtime(&clock);
9964 while(*p++ = *template++) if(p[-1] == '%') {
9965 switch(*template++) {
9966 case 0: *p = 0; return buf;
9967 case 'Y': i = tm->tm_year+1900; break;
9968 case 'y': i = tm->tm_year-100; break;
9969 case 'M': i = tm->tm_mon+1; break;
9970 case 'd': i = tm->tm_mday; break;
9971 case 'h': i = tm->tm_hour; break;
9972 case 'm': i = tm->tm_min; break;
9973 case 's': i = tm->tm_sec; break;
9976 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9982 CountPlayers (char *p)
9985 while(p = strchr(p, '\n')) p++, n++; // count participants
9990 WriteTourneyFile (char *results, FILE *f)
9991 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9992 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9993 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9994 // create a file with tournament description
9995 fprintf(f, "-participants {%s}\n", appData.participants);
9996 fprintf(f, "-seedBase %d\n", appData.seedBase);
9997 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9998 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9999 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10000 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10001 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10002 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10003 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10004 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10005 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10006 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10007 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10008 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10010 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10012 fprintf(f, "-mps %d\n", appData.movesPerSession);
10013 fprintf(f, "-tc %s\n", appData.timeControl);
10014 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10016 fprintf(f, "-results \"%s\"\n", results);
10021 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10024 Substitute (char *participants, int expunge)
10026 int i, changed, changes=0, nPlayers=0;
10027 char *p, *q, *r, buf[MSG_SIZ];
10028 if(participants == NULL) return;
10029 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10030 r = p = participants; q = appData.participants;
10031 while(*p && *p == *q) {
10032 if(*p == '\n') r = p+1, nPlayers++;
10035 if(*p) { // difference
10036 while(*p && *p++ != '\n');
10037 while(*q && *q++ != '\n');
10038 changed = nPlayers;
10039 changes = 1 + (strcmp(p, q) != 0);
10041 if(changes == 1) { // a single engine mnemonic was changed
10042 q = r; while(*q) nPlayers += (*q++ == '\n');
10043 p = buf; while(*r && (*p = *r++) != '\n') p++;
10045 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10046 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10047 if(mnemonic[i]) { // The substitute is valid
10049 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10050 flock(fileno(f), LOCK_EX);
10051 ParseArgsFromFile(f);
10052 fseek(f, 0, SEEK_SET);
10053 FREE(appData.participants); appData.participants = participants;
10054 if(expunge) { // erase results of replaced engine
10055 int len = strlen(appData.results), w, b, dummy;
10056 for(i=0; i<len; i++) {
10057 Pairing(i, nPlayers, &w, &b, &dummy);
10058 if((w == changed || b == changed) && appData.results[i] == '*') {
10059 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10064 for(i=0; i<len; i++) {
10065 Pairing(i, nPlayers, &w, &b, &dummy);
10066 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10069 WriteTourneyFile(appData.results, f);
10070 fclose(f); // release lock
10073 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10075 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10076 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10077 free(participants);
10082 CheckPlayers (char *participants)
10085 char buf[MSG_SIZ], *p;
10086 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10087 while(p = strchr(participants, '\n')) {
10089 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10091 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10093 DisplayError(buf, 0);
10097 participants = p + 1;
10103 CreateTourney (char *name)
10106 if(matchMode && strcmp(name, appData.tourneyFile)) {
10107 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10109 if(name[0] == NULLCHAR) {
10110 if(appData.participants[0])
10111 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10114 f = fopen(name, "r");
10115 if(f) { // file exists
10116 ASSIGN(appData.tourneyFile, name);
10117 ParseArgsFromFile(f); // parse it
10119 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10120 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10121 DisplayError(_("Not enough participants"), 0);
10124 if(CheckPlayers(appData.participants)) return 0;
10125 ASSIGN(appData.tourneyFile, name);
10126 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10127 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10130 appData.noChessProgram = FALSE;
10131 appData.clockMode = TRUE;
10137 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10139 char buf[MSG_SIZ], *p, *q;
10140 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10141 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10142 skip = !all && group[0]; // if group requested, we start in skip mode
10143 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10144 p = names; q = buf; header = 0;
10145 while(*p && *p != '\n') *q++ = *p++;
10147 if(*p == '\n') p++;
10148 if(buf[0] == '#') {
10149 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10150 depth++; // we must be entering a new group
10151 if(all) continue; // suppress printing group headers when complete list requested
10153 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10155 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10156 if(engineList[i]) free(engineList[i]);
10157 engineList[i] = strdup(buf);
10158 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10159 if(engineMnemonic[i]) free(engineMnemonic[i]);
10160 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10162 sscanf(q + 8, "%s", buf + strlen(buf));
10165 engineMnemonic[i] = strdup(buf);
10168 engineList[i] = engineMnemonic[i] = NULL;
10172 // following implemented as macro to avoid type limitations
10173 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10176 SwapEngines (int n)
10177 { // swap settings for first engine and other engine (so far only some selected options)
10182 SWAP(chessProgram, p)
10184 SWAP(hasOwnBookUCI, h)
10185 SWAP(protocolVersion, h)
10187 SWAP(scoreIsAbsolute, h)
10192 SWAP(engOptions, p)
10193 SWAP(engInitString, p)
10194 SWAP(computerString, p)
10196 SWAP(fenOverride, p)
10198 SWAP(accumulateTC, h)
10203 GetEngineLine (char *s, int n)
10207 extern char *icsNames;
10208 if(!s || !*s) return 0;
10209 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10210 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10211 if(!mnemonic[i]) return 0;
10212 if(n == 11) return 1; // just testing if there was a match
10213 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10214 if(n == 1) SwapEngines(n);
10215 ParseArgsFromString(buf);
10216 if(n == 1) SwapEngines(n);
10217 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10218 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10219 ParseArgsFromString(buf);
10225 SetPlayer (int player, char *p)
10226 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10228 char buf[MSG_SIZ], *engineName;
10229 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10230 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10231 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10233 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10234 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10235 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10236 ParseArgsFromString(buf);
10242 char *recentEngines;
10245 RecentEngineEvent (int nr)
10248 // SwapEngines(1); // bump first to second
10249 // ReplaceEngine(&second, 1); // and load it there
10250 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10251 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10252 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10253 ReplaceEngine(&first, 0);
10254 FloatToFront(&appData.recentEngineList, command[n]);
10259 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10260 { // determine players from game number
10261 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10263 if(appData.tourneyType == 0) {
10264 roundsPerCycle = (nPlayers - 1) | 1;
10265 pairingsPerRound = nPlayers / 2;
10266 } else if(appData.tourneyType > 0) {
10267 roundsPerCycle = nPlayers - appData.tourneyType;
10268 pairingsPerRound = appData.tourneyType;
10270 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10271 gamesPerCycle = gamesPerRound * roundsPerCycle;
10272 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10273 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10274 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10275 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10276 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10277 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10279 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10280 if(appData.roundSync) *syncInterval = gamesPerRound;
10282 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10284 if(appData.tourneyType == 0) {
10285 if(curPairing == (nPlayers-1)/2 ) {
10286 *whitePlayer = curRound;
10287 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10289 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10290 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10291 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10292 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10294 } else if(appData.tourneyType > 1) {
10295 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10296 *whitePlayer = curRound + appData.tourneyType;
10297 } else if(appData.tourneyType > 0) {
10298 *whitePlayer = curPairing;
10299 *blackPlayer = curRound + appData.tourneyType;
10302 // take care of white/black alternation per round.
10303 // For cycles and games this is already taken care of by default, derived from matchGame!
10304 return curRound & 1;
10308 NextTourneyGame (int nr, int *swapColors)
10309 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10311 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10313 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10314 tf = fopen(appData.tourneyFile, "r");
10315 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10316 ParseArgsFromFile(tf); fclose(tf);
10317 InitTimeControls(); // TC might be altered from tourney file
10319 nPlayers = CountPlayers(appData.participants); // count participants
10320 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10321 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10324 p = q = appData.results;
10325 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10326 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10327 DisplayMessage(_("Waiting for other game(s)"),"");
10328 waitingForGame = TRUE;
10329 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10332 waitingForGame = FALSE;
10335 if(appData.tourneyType < 0) {
10336 if(nr>=0 && !pairingReceived) {
10338 if(pairing.pr == NoProc) {
10339 if(!appData.pairingEngine[0]) {
10340 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10343 StartChessProgram(&pairing); // starts the pairing engine
10345 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10346 SendToProgram(buf, &pairing);
10347 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10348 SendToProgram(buf, &pairing);
10349 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10351 pairingReceived = 0; // ... so we continue here
10353 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10354 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10355 matchGame = 1; roundNr = nr / syncInterval + 1;
10358 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10360 // redefine engines, engine dir, etc.
10361 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10362 if(first.pr == NoProc) {
10363 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10364 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10366 if(second.pr == NoProc) {
10368 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10369 SwapEngines(1); // and make that valid for second engine by swapping
10370 InitEngine(&second, 1);
10372 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10373 UpdateLogos(FALSE); // leave display to ModeHiglight()
10379 { // performs game initialization that does not invoke engines, and then tries to start the game
10380 int res, firstWhite, swapColors = 0;
10381 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10382 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
10384 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10385 if(strcmp(buf, currentDebugFile)) { // name has changed
10386 FILE *f = fopen(buf, "w");
10387 if(f) { // if opening the new file failed, just keep using the old one
10388 ASSIGN(currentDebugFile, buf);
10392 if(appData.serverFileName) {
10393 if(serverFP) fclose(serverFP);
10394 serverFP = fopen(appData.serverFileName, "w");
10395 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10396 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10400 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10401 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10402 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10403 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10404 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10405 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10406 Reset(FALSE, first.pr != NoProc);
10407 res = LoadGameOrPosition(matchGame); // setup game
10408 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10409 if(!res) return; // abort when bad game/pos file
10410 TwoMachinesEvent();
10414 UserAdjudicationEvent (int result)
10416 ChessMove gameResult = GameIsDrawn;
10419 gameResult = WhiteWins;
10421 else if( result < 0 ) {
10422 gameResult = BlackWins;
10425 if( gameMode == TwoMachinesPlay ) {
10426 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10431 // [HGM] save: calculate checksum of game to make games easily identifiable
10433 StringCheckSum (char *s)
10436 if(s==NULL) return 0;
10437 while(*s) i = i*259 + *s++;
10445 for(i=backwardMostMove; i<forwardMostMove; i++) {
10446 sum += pvInfoList[i].depth;
10447 sum += StringCheckSum(parseList[i]);
10448 sum += StringCheckSum(commentList[i]);
10451 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10452 return sum + StringCheckSum(commentList[i]);
10453 } // end of save patch
10456 GameEnds (ChessMove result, char *resultDetails, int whosays)
10458 GameMode nextGameMode;
10460 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10462 if(endingGame) return; /* [HGM] crash: forbid recursion */
10464 if(twoBoards) { // [HGM] dual: switch back to one board
10465 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10466 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10468 if (appData.debugMode) {
10469 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10470 result, resultDetails ? resultDetails : "(null)", whosays);
10473 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10475 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10476 /* If we are playing on ICS, the server decides when the
10477 game is over, but the engine can offer to draw, claim
10481 if (appData.zippyPlay && first.initDone) {
10482 if (result == GameIsDrawn) {
10483 /* In case draw still needs to be claimed */
10484 SendToICS(ics_prefix);
10485 SendToICS("draw\n");
10486 } else if (StrCaseStr(resultDetails, "resign")) {
10487 SendToICS(ics_prefix);
10488 SendToICS("resign\n");
10492 endingGame = 0; /* [HGM] crash */
10496 /* If we're loading the game from a file, stop */
10497 if (whosays == GE_FILE) {
10498 (void) StopLoadGameTimer();
10502 /* Cancel draw offers */
10503 first.offeredDraw = second.offeredDraw = 0;
10505 /* If this is an ICS game, only ICS can really say it's done;
10506 if not, anyone can. */
10507 isIcsGame = (gameMode == IcsPlayingWhite ||
10508 gameMode == IcsPlayingBlack ||
10509 gameMode == IcsObserving ||
10510 gameMode == IcsExamining);
10512 if (!isIcsGame || whosays == GE_ICS) {
10513 /* OK -- not an ICS game, or ICS said it was done */
10515 if (!isIcsGame && !appData.noChessProgram)
10516 SetUserThinkingEnables();
10518 /* [HGM] if a machine claims the game end we verify this claim */
10519 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10520 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10522 ChessMove trueResult = (ChessMove) -1;
10524 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10525 first.twoMachinesColor[0] :
10526 second.twoMachinesColor[0] ;
10528 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10529 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10530 /* [HGM] verify: engine mate claims accepted if they were flagged */
10531 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10533 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10534 /* [HGM] verify: engine mate claims accepted if they were flagged */
10535 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10537 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10538 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10541 // now verify win claims, but not in drop games, as we don't understand those yet
10542 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10543 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10544 (result == WhiteWins && claimer == 'w' ||
10545 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10546 if (appData.debugMode) {
10547 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10548 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10550 if(result != trueResult) {
10551 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10552 result = claimer == 'w' ? BlackWins : WhiteWins;
10553 resultDetails = buf;
10556 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10557 && (forwardMostMove <= backwardMostMove ||
10558 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10559 (claimer=='b')==(forwardMostMove&1))
10561 /* [HGM] verify: draws that were not flagged are false claims */
10562 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10563 result = claimer == 'w' ? BlackWins : WhiteWins;
10564 resultDetails = buf;
10566 /* (Claiming a loss is accepted no questions asked!) */
10568 /* [HGM] bare: don't allow bare King to win */
10569 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10570 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10571 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10572 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10573 && result != GameIsDrawn)
10574 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10575 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10576 int p = (signed char)boards[forwardMostMove][i][j] - color;
10577 if(p >= 0 && p <= (int)WhiteKing) k++;
10579 if (appData.debugMode) {
10580 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10581 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10584 result = GameIsDrawn;
10585 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10586 resultDetails = buf;
10592 if(serverMoves != NULL && !loadFlag) { char c = '=';
10593 if(result==WhiteWins) c = '+';
10594 if(result==BlackWins) c = '-';
10595 if(resultDetails != NULL)
10596 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10598 if (resultDetails != NULL) {
10599 gameInfo.result = result;
10600 gameInfo.resultDetails = StrSave(resultDetails);
10602 /* display last move only if game was not loaded from file */
10603 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10604 DisplayMove(currentMove - 1);
10606 if (forwardMostMove != 0) {
10607 if (gameMode != PlayFromGameFile && gameMode != EditGame
10608 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10610 if (*appData.saveGameFile != NULLCHAR) {
10611 SaveGameToFile(appData.saveGameFile, TRUE);
10612 } else if (appData.autoSaveGames) {
10615 if (*appData.savePositionFile != NULLCHAR) {
10616 SavePositionToFile(appData.savePositionFile);
10621 /* Tell program how game ended in case it is learning */
10622 /* [HGM] Moved this to after saving the PGN, just in case */
10623 /* engine died and we got here through time loss. In that */
10624 /* case we will get a fatal error writing the pipe, which */
10625 /* would otherwise lose us the PGN. */
10626 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10627 /* output during GameEnds should never be fatal anymore */
10628 if (gameMode == MachinePlaysWhite ||
10629 gameMode == MachinePlaysBlack ||
10630 gameMode == TwoMachinesPlay ||
10631 gameMode == IcsPlayingWhite ||
10632 gameMode == IcsPlayingBlack ||
10633 gameMode == BeginningOfGame) {
10635 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10637 if (first.pr != NoProc) {
10638 SendToProgram(buf, &first);
10640 if (second.pr != NoProc &&
10641 gameMode == TwoMachinesPlay) {
10642 SendToProgram(buf, &second);
10647 if (appData.icsActive) {
10648 if (appData.quietPlay &&
10649 (gameMode == IcsPlayingWhite ||
10650 gameMode == IcsPlayingBlack)) {
10651 SendToICS(ics_prefix);
10652 SendToICS("set shout 1\n");
10654 nextGameMode = IcsIdle;
10655 ics_user_moved = FALSE;
10656 /* clean up premove. It's ugly when the game has ended and the
10657 * premove highlights are still on the board.
10660 gotPremove = FALSE;
10661 ClearPremoveHighlights();
10662 DrawPosition(FALSE, boards[currentMove]);
10664 if (whosays == GE_ICS) {
10667 if (gameMode == IcsPlayingWhite)
10669 else if(gameMode == IcsPlayingBlack)
10670 PlayIcsLossSound();
10673 if (gameMode == IcsPlayingBlack)
10675 else if(gameMode == IcsPlayingWhite)
10676 PlayIcsLossSound();
10679 PlayIcsDrawSound();
10682 PlayIcsUnfinishedSound();
10685 } else if (gameMode == EditGame ||
10686 gameMode == PlayFromGameFile ||
10687 gameMode == AnalyzeMode ||
10688 gameMode == AnalyzeFile) {
10689 nextGameMode = gameMode;
10691 nextGameMode = EndOfGame;
10696 nextGameMode = gameMode;
10699 if (appData.noChessProgram) {
10700 gameMode = nextGameMode;
10702 endingGame = 0; /* [HGM] crash */
10707 /* Put first chess program into idle state */
10708 if (first.pr != NoProc &&
10709 (gameMode == MachinePlaysWhite ||
10710 gameMode == MachinePlaysBlack ||
10711 gameMode == TwoMachinesPlay ||
10712 gameMode == IcsPlayingWhite ||
10713 gameMode == IcsPlayingBlack ||
10714 gameMode == BeginningOfGame)) {
10715 SendToProgram("force\n", &first);
10716 if (first.usePing) {
10718 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10719 SendToProgram(buf, &first);
10722 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10723 /* Kill off first chess program */
10724 if (first.isr != NULL)
10725 RemoveInputSource(first.isr);
10728 if (first.pr != NoProc) {
10730 DoSleep( appData.delayBeforeQuit );
10731 SendToProgram("quit\n", &first);
10732 DoSleep( appData.delayAfterQuit );
10733 DestroyChildProcess(first.pr, first.useSigterm);
10737 if (second.reuse) {
10738 /* Put second chess program into idle state */
10739 if (second.pr != NoProc &&
10740 gameMode == TwoMachinesPlay) {
10741 SendToProgram("force\n", &second);
10742 if (second.usePing) {
10744 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10745 SendToProgram(buf, &second);
10748 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10749 /* Kill off second chess program */
10750 if (second.isr != NULL)
10751 RemoveInputSource(second.isr);
10754 if (second.pr != NoProc) {
10755 DoSleep( appData.delayBeforeQuit );
10756 SendToProgram("quit\n", &second);
10757 DoSleep( appData.delayAfterQuit );
10758 DestroyChildProcess(second.pr, second.useSigterm);
10760 second.pr = NoProc;
10763 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10764 char resChar = '=';
10768 if (first.twoMachinesColor[0] == 'w') {
10771 second.matchWins++;
10776 if (first.twoMachinesColor[0] == 'b') {
10779 second.matchWins++;
10782 case GameUnfinished:
10788 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10789 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10790 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10791 ReserveGame(nextGame, resChar); // sets nextGame
10792 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10793 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10794 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10796 if (nextGame <= appData.matchGames && !abortMatch) {
10797 gameMode = nextGameMode;
10798 matchGame = nextGame; // this will be overruled in tourney mode!
10799 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10800 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10801 endingGame = 0; /* [HGM] crash */
10804 gameMode = nextGameMode;
10805 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10806 first.tidy, second.tidy,
10807 first.matchWins, second.matchWins,
10808 appData.matchGames - (first.matchWins + second.matchWins));
10809 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10810 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10811 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10812 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10813 first.twoMachinesColor = "black\n";
10814 second.twoMachinesColor = "white\n";
10816 first.twoMachinesColor = "white\n";
10817 second.twoMachinesColor = "black\n";
10821 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10822 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10824 gameMode = nextGameMode;
10826 endingGame = 0; /* [HGM] crash */
10827 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10828 if(matchMode == TRUE) { // match through command line: exit with or without popup
10830 ToNrEvent(forwardMostMove);
10831 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10833 } else DisplayFatalError(buf, 0, 0);
10834 } else { // match through menu; just stop, with or without popup
10835 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10838 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10839 } else DisplayNote(buf);
10841 if(ranking) free(ranking);
10845 /* Assumes program was just initialized (initString sent).
10846 Leaves program in force mode. */
10848 FeedMovesToProgram (ChessProgramState *cps, int upto)
10852 if (appData.debugMode)
10853 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10854 startedFromSetupPosition ? "position and " : "",
10855 backwardMostMove, upto, cps->which);
10856 if(currentlyInitializedVariant != gameInfo.variant) {
10858 // [HGM] variantswitch: make engine aware of new variant
10859 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10860 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10861 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10862 SendToProgram(buf, cps);
10863 currentlyInitializedVariant = gameInfo.variant;
10865 SendToProgram("force\n", cps);
10866 if (startedFromSetupPosition) {
10867 SendBoard(cps, backwardMostMove);
10868 if (appData.debugMode) {
10869 fprintf(debugFP, "feedMoves\n");
10872 for (i = backwardMostMove; i < upto; i++) {
10873 SendMoveToProgram(i, cps);
10879 ResurrectChessProgram ()
10881 /* The chess program may have exited.
10882 If so, restart it and feed it all the moves made so far. */
10883 static int doInit = 0;
10885 if (appData.noChessProgram) return 1;
10887 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10888 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10889 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10890 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10892 if (first.pr != NoProc) return 1;
10893 StartChessProgram(&first);
10895 InitChessProgram(&first, FALSE);
10896 FeedMovesToProgram(&first, currentMove);
10898 if (!first.sendTime) {
10899 /* can't tell gnuchess what its clock should read,
10900 so we bow to its notion. */
10902 timeRemaining[0][currentMove] = whiteTimeRemaining;
10903 timeRemaining[1][currentMove] = blackTimeRemaining;
10906 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10907 appData.icsEngineAnalyze) && first.analysisSupport) {
10908 SendToProgram("analyze\n", &first);
10909 first.analyzing = TRUE;
10915 * Button procedures
10918 Reset (int redraw, int init)
10922 if (appData.debugMode) {
10923 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10924 redraw, init, gameMode);
10926 CleanupTail(); // [HGM] vari: delete any stored variations
10927 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10928 pausing = pauseExamInvalid = FALSE;
10929 startedFromSetupPosition = blackPlaysFirst = FALSE;
10931 whiteFlag = blackFlag = FALSE;
10932 userOfferedDraw = FALSE;
10933 hintRequested = bookRequested = FALSE;
10934 first.maybeThinking = FALSE;
10935 second.maybeThinking = FALSE;
10936 first.bookSuspend = FALSE; // [HGM] book
10937 second.bookSuspend = FALSE;
10938 thinkOutput[0] = NULLCHAR;
10939 lastHint[0] = NULLCHAR;
10940 ClearGameInfo(&gameInfo);
10941 gameInfo.variant = StringToVariant(appData.variant);
10942 ics_user_moved = ics_clock_paused = FALSE;
10943 ics_getting_history = H_FALSE;
10945 white_holding[0] = black_holding[0] = NULLCHAR;
10946 ClearProgramStats();
10947 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10951 flipView = appData.flipView;
10952 ClearPremoveHighlights();
10953 gotPremove = FALSE;
10954 alarmSounded = FALSE;
10956 GameEnds(EndOfFile, NULL, GE_PLAYER);
10957 if(appData.serverMovesName != NULL) {
10958 /* [HGM] prepare to make moves file for broadcasting */
10959 clock_t t = clock();
10960 if(serverMoves != NULL) fclose(serverMoves);
10961 serverMoves = fopen(appData.serverMovesName, "r");
10962 if(serverMoves != NULL) {
10963 fclose(serverMoves);
10964 /* delay 15 sec before overwriting, so all clients can see end */
10965 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10967 serverMoves = fopen(appData.serverMovesName, "w");
10971 gameMode = BeginningOfGame;
10973 if(appData.icsActive) gameInfo.variant = VariantNormal;
10974 currentMove = forwardMostMove = backwardMostMove = 0;
10975 MarkTargetSquares(1);
10976 InitPosition(redraw);
10977 for (i = 0; i < MAX_MOVES; i++) {
10978 if (commentList[i] != NULL) {
10979 free(commentList[i]);
10980 commentList[i] = NULL;
10984 timeRemaining[0][0] = whiteTimeRemaining;
10985 timeRemaining[1][0] = blackTimeRemaining;
10987 if (first.pr == NoProc) {
10988 StartChessProgram(&first);
10991 InitChessProgram(&first, startedFromSetupPosition);
10994 DisplayMessage("", "");
10995 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10996 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10997 ClearMap(); // [HGM] exclude: invalidate map
11001 AutoPlayGameLoop ()
11004 if (!AutoPlayOneMove())
11006 if (matchMode || appData.timeDelay == 0)
11008 if (appData.timeDelay < 0)
11010 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11018 ReloadGame(1); // next game
11024 int fromX, fromY, toX, toY;
11026 if (appData.debugMode) {
11027 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11030 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11033 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11034 pvInfoList[currentMove].depth = programStats.depth;
11035 pvInfoList[currentMove].score = programStats.score;
11036 pvInfoList[currentMove].time = 0;
11037 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11040 if (currentMove >= forwardMostMove) {
11041 if(gameMode == AnalyzeFile) {
11042 if(appData.loadGameIndex == -1) {
11043 GameEnds(EndOfFile, NULL, GE_FILE);
11044 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11046 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11049 // gameMode = EndOfGame;
11050 // ModeHighlight();
11052 /* [AS] Clear current move marker at the end of a game */
11053 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11058 toX = moveList[currentMove][2] - AAA;
11059 toY = moveList[currentMove][3] - ONE;
11061 if (moveList[currentMove][1] == '@') {
11062 if (appData.highlightLastMove) {
11063 SetHighlights(-1, -1, toX, toY);
11066 fromX = moveList[currentMove][0] - AAA;
11067 fromY = moveList[currentMove][1] - ONE;
11069 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11071 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11073 if (appData.highlightLastMove) {
11074 SetHighlights(fromX, fromY, toX, toY);
11077 DisplayMove(currentMove);
11078 SendMoveToProgram(currentMove++, &first);
11079 DisplayBothClocks();
11080 DrawPosition(FALSE, boards[currentMove]);
11081 // [HGM] PV info: always display, routine tests if empty
11082 DisplayComment(currentMove - 1, commentList[currentMove]);
11088 LoadGameOneMove (ChessMove readAhead)
11090 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11091 char promoChar = NULLCHAR;
11092 ChessMove moveType;
11093 char move[MSG_SIZ];
11096 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11097 gameMode != AnalyzeMode && gameMode != Training) {
11102 yyboardindex = forwardMostMove;
11103 if (readAhead != EndOfFile) {
11104 moveType = readAhead;
11106 if (gameFileFP == NULL)
11108 moveType = (ChessMove) Myylex();
11112 switch (moveType) {
11114 if (appData.debugMode)
11115 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11118 /* append the comment but don't display it */
11119 AppendComment(currentMove, p, FALSE);
11122 case WhiteCapturesEnPassant:
11123 case BlackCapturesEnPassant:
11124 case WhitePromotion:
11125 case BlackPromotion:
11126 case WhiteNonPromotion:
11127 case BlackNonPromotion:
11129 case WhiteKingSideCastle:
11130 case WhiteQueenSideCastle:
11131 case BlackKingSideCastle:
11132 case BlackQueenSideCastle:
11133 case WhiteKingSideCastleWild:
11134 case WhiteQueenSideCastleWild:
11135 case BlackKingSideCastleWild:
11136 case BlackQueenSideCastleWild:
11138 case WhiteHSideCastleFR:
11139 case WhiteASideCastleFR:
11140 case BlackHSideCastleFR:
11141 case BlackASideCastleFR:
11143 if (appData.debugMode)
11144 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11145 fromX = currentMoveString[0] - AAA;
11146 fromY = currentMoveString[1] - ONE;
11147 toX = currentMoveString[2] - AAA;
11148 toY = currentMoveString[3] - ONE;
11149 promoChar = currentMoveString[4];
11154 if (appData.debugMode)
11155 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11156 fromX = moveType == WhiteDrop ?
11157 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11158 (int) CharToPiece(ToLower(currentMoveString[0]));
11160 toX = currentMoveString[2] - AAA;
11161 toY = currentMoveString[3] - ONE;
11167 case GameUnfinished:
11168 if (appData.debugMode)
11169 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11170 p = strchr(yy_text, '{');
11171 if (p == NULL) p = strchr(yy_text, '(');
11174 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11176 q = strchr(p, *p == '{' ? '}' : ')');
11177 if (q != NULL) *q = NULLCHAR;
11180 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11181 GameEnds(moveType, p, GE_FILE);
11183 if (cmailMsgLoaded) {
11185 flipView = WhiteOnMove(currentMove);
11186 if (moveType == GameUnfinished) flipView = !flipView;
11187 if (appData.debugMode)
11188 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11193 if (appData.debugMode)
11194 fprintf(debugFP, "Parser hit end of file\n");
11195 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11201 if (WhiteOnMove(currentMove)) {
11202 GameEnds(BlackWins, "Black mates", GE_FILE);
11204 GameEnds(WhiteWins, "White mates", GE_FILE);
11208 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11214 case MoveNumberOne:
11215 if (lastLoadGameStart == GNUChessGame) {
11216 /* GNUChessGames have numbers, but they aren't move numbers */
11217 if (appData.debugMode)
11218 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11219 yy_text, (int) moveType);
11220 return LoadGameOneMove(EndOfFile); /* tail recursion */
11222 /* else fall thru */
11227 /* Reached start of next game in file */
11228 if (appData.debugMode)
11229 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11230 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11236 if (WhiteOnMove(currentMove)) {
11237 GameEnds(BlackWins, "Black mates", GE_FILE);
11239 GameEnds(WhiteWins, "White mates", GE_FILE);
11243 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11249 case PositionDiagram: /* should not happen; ignore */
11250 case ElapsedTime: /* ignore */
11251 case NAG: /* ignore */
11252 if (appData.debugMode)
11253 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11254 yy_text, (int) moveType);
11255 return LoadGameOneMove(EndOfFile); /* tail recursion */
11258 if (appData.testLegality) {
11259 if (appData.debugMode)
11260 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11261 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11262 (forwardMostMove / 2) + 1,
11263 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11264 DisplayError(move, 0);
11267 if (appData.debugMode)
11268 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11269 yy_text, currentMoveString);
11270 fromX = currentMoveString[0] - AAA;
11271 fromY = currentMoveString[1] - ONE;
11272 toX = currentMoveString[2] - AAA;
11273 toY = currentMoveString[3] - ONE;
11274 promoChar = currentMoveString[4];
11278 case AmbiguousMove:
11279 if (appData.debugMode)
11280 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11281 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11282 (forwardMostMove / 2) + 1,
11283 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11284 DisplayError(move, 0);
11289 case ImpossibleMove:
11290 if (appData.debugMode)
11291 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11292 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11293 (forwardMostMove / 2) + 1,
11294 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11295 DisplayError(move, 0);
11301 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11302 DrawPosition(FALSE, boards[currentMove]);
11303 DisplayBothClocks();
11304 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11305 DisplayComment(currentMove - 1, commentList[currentMove]);
11307 (void) StopLoadGameTimer();
11309 cmailOldMove = forwardMostMove;
11312 /* currentMoveString is set as a side-effect of yylex */
11314 thinkOutput[0] = NULLCHAR;
11315 MakeMove(fromX, fromY, toX, toY, promoChar);
11316 currentMove = forwardMostMove;
11321 /* Load the nth game from the given file */
11323 LoadGameFromFile (char *filename, int n, char *title, int useList)
11328 if (strcmp(filename, "-") == 0) {
11332 f = fopen(filename, "rb");
11334 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11335 DisplayError(buf, errno);
11339 if (fseek(f, 0, 0) == -1) {
11340 /* f is not seekable; probably a pipe */
11343 if (useList && n == 0) {
11344 int error = GameListBuild(f);
11346 DisplayError(_("Cannot build game list"), error);
11347 } else if (!ListEmpty(&gameList) &&
11348 ((ListGame *) gameList.tailPred)->number > 1) {
11349 GameListPopUp(f, title);
11356 return LoadGame(f, n, title, FALSE);
11361 MakeRegisteredMove ()
11363 int fromX, fromY, toX, toY;
11365 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11366 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11369 if (appData.debugMode)
11370 fprintf(debugFP, "Restoring %s for game %d\n",
11371 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11373 thinkOutput[0] = NULLCHAR;
11374 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11375 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11376 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11377 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11378 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11379 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11380 MakeMove(fromX, fromY, toX, toY, promoChar);
11381 ShowMove(fromX, fromY, toX, toY);
11383 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11390 if (WhiteOnMove(currentMove)) {
11391 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11393 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11398 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11405 if (WhiteOnMove(currentMove)) {
11406 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11408 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11413 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11424 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11426 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11430 if (gameNumber > nCmailGames) {
11431 DisplayError(_("No more games in this message"), 0);
11434 if (f == lastLoadGameFP) {
11435 int offset = gameNumber - lastLoadGameNumber;
11437 cmailMsg[0] = NULLCHAR;
11438 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11439 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11440 nCmailMovesRegistered--;
11442 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11443 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11444 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11447 if (! RegisterMove()) return FALSE;
11451 retVal = LoadGame(f, gameNumber, title, useList);
11453 /* Make move registered during previous look at this game, if any */
11454 MakeRegisteredMove();
11456 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11457 commentList[currentMove]
11458 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11459 DisplayComment(currentMove - 1, commentList[currentMove]);
11465 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11467 ReloadGame (int offset)
11469 int gameNumber = lastLoadGameNumber + offset;
11470 if (lastLoadGameFP == NULL) {
11471 DisplayError(_("No game has been loaded yet"), 0);
11474 if (gameNumber <= 0) {
11475 DisplayError(_("Can't back up any further"), 0);
11478 if (cmailMsgLoaded) {
11479 return CmailLoadGame(lastLoadGameFP, gameNumber,
11480 lastLoadGameTitle, lastLoadGameUseList);
11482 return LoadGame(lastLoadGameFP, gameNumber,
11483 lastLoadGameTitle, lastLoadGameUseList);
11487 int keys[EmptySquare+1];
11490 PositionMatches (Board b1, Board b2)
11493 switch(appData.searchMode) {
11494 case 1: return CompareWithRights(b1, b2);
11496 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11497 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11501 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11502 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11503 sum += keys[b1[r][f]] - keys[b2[r][f]];
11507 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11508 sum += keys[b1[r][f]] - keys[b2[r][f]];
11520 int pieceList[256], quickBoard[256];
11521 ChessSquare pieceType[256] = { EmptySquare };
11522 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11523 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11524 int soughtTotal, turn;
11525 Boolean epOK, flipSearch;
11528 unsigned char piece, to;
11531 #define DSIZE (250000)
11533 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11534 Move *moveDatabase = initialSpace;
11535 unsigned int movePtr, dataSize = DSIZE;
11538 MakePieceList (Board board, int *counts)
11540 int r, f, n=Q_PROMO, total=0;
11541 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11542 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11543 int sq = f + (r<<4);
11544 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11545 quickBoard[sq] = ++n;
11547 pieceType[n] = board[r][f];
11548 counts[board[r][f]]++;
11549 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11550 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11554 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11559 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11561 int sq = fromX + (fromY<<4);
11562 int piece = quickBoard[sq];
11563 quickBoard[sq] = 0;
11564 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11565 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11566 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11567 moveDatabase[movePtr++].piece = Q_WCASTL;
11568 quickBoard[sq] = piece;
11569 piece = quickBoard[from]; quickBoard[from] = 0;
11570 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11572 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11573 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11574 moveDatabase[movePtr++].piece = Q_BCASTL;
11575 quickBoard[sq] = piece;
11576 piece = quickBoard[from]; quickBoard[from] = 0;
11577 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11579 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11580 quickBoard[(fromY<<4)+toX] = 0;
11581 moveDatabase[movePtr].piece = Q_EP;
11582 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11583 moveDatabase[movePtr].to = sq;
11585 if(promoPiece != pieceType[piece]) {
11586 moveDatabase[movePtr++].piece = Q_PROMO;
11587 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11589 moveDatabase[movePtr].piece = piece;
11590 quickBoard[sq] = piece;
11595 PackGame (Board board)
11597 Move *newSpace = NULL;
11598 moveDatabase[movePtr].piece = 0; // terminate previous game
11599 if(movePtr > dataSize) {
11600 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11601 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11602 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11605 Move *p = moveDatabase, *q = newSpace;
11606 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11607 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11608 moveDatabase = newSpace;
11609 } else { // calloc failed, we must be out of memory. Too bad...
11610 dataSize = 0; // prevent calloc events for all subsequent games
11611 return 0; // and signal this one isn't cached
11615 MakePieceList(board, counts);
11620 QuickCompare (Board board, int *minCounts, int *maxCounts)
11621 { // compare according to search mode
11623 switch(appData.searchMode)
11625 case 1: // exact position match
11626 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11627 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11628 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11631 case 2: // can have extra material on empty squares
11632 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11633 if(board[r][f] == EmptySquare) continue;
11634 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11637 case 3: // material with exact Pawn structure
11638 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11639 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11640 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11641 } // fall through to material comparison
11642 case 4: // exact material
11643 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11645 case 6: // material range with given imbalance
11646 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11647 // fall through to range comparison
11648 case 5: // material range
11649 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11655 QuickScan (Board board, Move *move)
11656 { // reconstruct game,and compare all positions in it
11657 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11659 int piece = move->piece;
11660 int to = move->to, from = pieceList[piece];
11661 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11662 if(!piece) return -1;
11663 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11664 piece = (++move)->piece;
11665 from = pieceList[piece];
11666 counts[pieceType[piece]]--;
11667 pieceType[piece] = (ChessSquare) move->to;
11668 counts[move->to]++;
11669 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11670 counts[pieceType[quickBoard[to]]]--;
11671 quickBoard[to] = 0; total--;
11674 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11675 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11676 from = pieceList[piece]; // so this must be King
11677 quickBoard[from] = 0;
11678 pieceList[piece] = to;
11679 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11680 quickBoard[from] = 0; // rook
11681 quickBoard[to] = piece;
11682 to = move->to; piece = move->piece;
11686 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11687 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11688 quickBoard[from] = 0;
11690 quickBoard[to] = piece;
11691 pieceList[piece] = to;
11693 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11694 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11695 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11696 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11698 static int lastCounts[EmptySquare+1];
11700 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11701 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11702 } else stretch = 0;
11703 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11712 flipSearch = FALSE;
11713 CopyBoard(soughtBoard, boards[currentMove]);
11714 soughtTotal = MakePieceList(soughtBoard, maxSought);
11715 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11716 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11717 CopyBoard(reverseBoard, boards[currentMove]);
11718 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11719 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11720 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11721 reverseBoard[r][f] = piece;
11723 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11724 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11725 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11726 || (boards[currentMove][CASTLING][2] == NoRights ||
11727 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11728 && (boards[currentMove][CASTLING][5] == NoRights ||
11729 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11732 CopyBoard(flipBoard, soughtBoard);
11733 CopyBoard(rotateBoard, reverseBoard);
11734 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11735 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11736 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11739 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11740 if(appData.searchMode >= 5) {
11741 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11742 MakePieceList(soughtBoard, minSought);
11743 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11745 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11746 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11749 GameInfo dummyInfo;
11752 GameContainsPosition (FILE *f, ListGame *lg)
11754 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11755 int fromX, fromY, toX, toY;
11757 static int initDone=FALSE;
11759 // weed out games based on numerical tag comparison
11760 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11761 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11762 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11763 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11765 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11768 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11769 else CopyBoard(boards[scratch], initialPosition); // default start position
11772 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11773 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11776 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11777 fseek(f, lg->offset, 0);
11780 yyboardindex = scratch;
11781 quickFlag = plyNr+1;
11786 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11792 if(plyNr) return -1; // after we have seen moves, this is for new game
11795 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11796 case ImpossibleMove:
11797 case WhiteWins: // game ends here with these four
11800 case GameUnfinished:
11804 if(appData.testLegality) return -1;
11805 case WhiteCapturesEnPassant:
11806 case BlackCapturesEnPassant:
11807 case WhitePromotion:
11808 case BlackPromotion:
11809 case WhiteNonPromotion:
11810 case BlackNonPromotion:
11812 case WhiteKingSideCastle:
11813 case WhiteQueenSideCastle:
11814 case BlackKingSideCastle:
11815 case BlackQueenSideCastle:
11816 case WhiteKingSideCastleWild:
11817 case WhiteQueenSideCastleWild:
11818 case BlackKingSideCastleWild:
11819 case BlackQueenSideCastleWild:
11820 case WhiteHSideCastleFR:
11821 case WhiteASideCastleFR:
11822 case BlackHSideCastleFR:
11823 case BlackASideCastleFR:
11824 fromX = currentMoveString[0] - AAA;
11825 fromY = currentMoveString[1] - ONE;
11826 toX = currentMoveString[2] - AAA;
11827 toY = currentMoveString[3] - ONE;
11828 promoChar = currentMoveString[4];
11832 fromX = next == WhiteDrop ?
11833 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11834 (int) CharToPiece(ToLower(currentMoveString[0]));
11836 toX = currentMoveString[2] - AAA;
11837 toY = currentMoveString[3] - ONE;
11841 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11843 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11844 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11845 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11846 if(appData.findMirror) {
11847 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11848 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11853 /* Load the nth game from open file f */
11855 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11859 int gn = gameNumber;
11860 ListGame *lg = NULL;
11861 int numPGNTags = 0;
11863 GameMode oldGameMode;
11864 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11866 if (appData.debugMode)
11867 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11869 if (gameMode == Training )
11870 SetTrainingModeOff();
11872 oldGameMode = gameMode;
11873 if (gameMode != BeginningOfGame) {
11874 Reset(FALSE, TRUE);
11878 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11879 fclose(lastLoadGameFP);
11883 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11886 fseek(f, lg->offset, 0);
11887 GameListHighlight(gameNumber);
11888 pos = lg->position;
11892 if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11893 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11895 DisplayError(_("Game number out of range"), 0);
11900 if (fseek(f, 0, 0) == -1) {
11901 if (f == lastLoadGameFP ?
11902 gameNumber == lastLoadGameNumber + 1 :
11906 DisplayError(_("Can't seek on game file"), 0);
11911 lastLoadGameFP = f;
11912 lastLoadGameNumber = gameNumber;
11913 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11914 lastLoadGameUseList = useList;
11918 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11919 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11920 lg->gameInfo.black);
11922 } else if (*title != NULLCHAR) {
11923 if (gameNumber > 1) {
11924 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11927 DisplayTitle(title);
11931 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11932 gameMode = PlayFromGameFile;
11936 currentMove = forwardMostMove = backwardMostMove = 0;
11937 CopyBoard(boards[0], initialPosition);
11941 * Skip the first gn-1 games in the file.
11942 * Also skip over anything that precedes an identifiable
11943 * start of game marker, to avoid being confused by
11944 * garbage at the start of the file. Currently
11945 * recognized start of game markers are the move number "1",
11946 * the pattern "gnuchess .* game", the pattern
11947 * "^[#;%] [^ ]* game file", and a PGN tag block.
11948 * A game that starts with one of the latter two patterns
11949 * will also have a move number 1, possibly
11950 * following a position diagram.
11951 * 5-4-02: Let's try being more lenient and allowing a game to
11952 * start with an unnumbered move. Does that break anything?
11954 cm = lastLoadGameStart = EndOfFile;
11956 yyboardindex = forwardMostMove;
11957 cm = (ChessMove) Myylex();
11960 if (cmailMsgLoaded) {
11961 nCmailGames = CMAIL_MAX_GAMES - gn;
11964 DisplayError(_("Game not found in file"), 0);
11971 lastLoadGameStart = cm;
11974 case MoveNumberOne:
11975 switch (lastLoadGameStart) {
11980 case MoveNumberOne:
11982 gn--; /* count this game */
11983 lastLoadGameStart = cm;
11992 switch (lastLoadGameStart) {
11995 case MoveNumberOne:
11997 gn--; /* count this game */
11998 lastLoadGameStart = cm;
12001 lastLoadGameStart = cm; /* game counted already */
12009 yyboardindex = forwardMostMove;
12010 cm = (ChessMove) Myylex();
12011 } while (cm == PGNTag || cm == Comment);
12018 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12019 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12020 != CMAIL_OLD_RESULT) {
12022 cmailResult[ CMAIL_MAX_GAMES
12023 - gn - 1] = CMAIL_OLD_RESULT;
12029 /* Only a NormalMove can be at the start of a game
12030 * without a position diagram. */
12031 if (lastLoadGameStart == EndOfFile ) {
12033 lastLoadGameStart = MoveNumberOne;
12042 if (appData.debugMode)
12043 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12045 if (cm == XBoardGame) {
12046 /* Skip any header junk before position diagram and/or move 1 */
12048 yyboardindex = forwardMostMove;
12049 cm = (ChessMove) Myylex();
12051 if (cm == EndOfFile ||
12052 cm == GNUChessGame || cm == XBoardGame) {
12053 /* Empty game; pretend end-of-file and handle later */
12058 if (cm == MoveNumberOne || cm == PositionDiagram ||
12059 cm == PGNTag || cm == Comment)
12062 } else if (cm == GNUChessGame) {
12063 if (gameInfo.event != NULL) {
12064 free(gameInfo.event);
12066 gameInfo.event = StrSave(yy_text);
12069 startedFromSetupPosition = FALSE;
12070 while (cm == PGNTag) {
12071 if (appData.debugMode)
12072 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12073 err = ParsePGNTag(yy_text, &gameInfo);
12074 if (!err) numPGNTags++;
12076 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12077 if(gameInfo.variant != oldVariant) {
12078 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12079 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12080 InitPosition(TRUE);
12081 oldVariant = gameInfo.variant;
12082 if (appData.debugMode)
12083 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12087 if (gameInfo.fen != NULL) {
12088 Board initial_position;
12089 startedFromSetupPosition = TRUE;
12090 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12092 DisplayError(_("Bad FEN position in file"), 0);
12095 CopyBoard(boards[0], initial_position);
12096 if (blackPlaysFirst) {
12097 currentMove = forwardMostMove = backwardMostMove = 1;
12098 CopyBoard(boards[1], initial_position);
12099 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12100 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12101 timeRemaining[0][1] = whiteTimeRemaining;
12102 timeRemaining[1][1] = blackTimeRemaining;
12103 if (commentList[0] != NULL) {
12104 commentList[1] = commentList[0];
12105 commentList[0] = NULL;
12108 currentMove = forwardMostMove = backwardMostMove = 0;
12110 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12112 initialRulePlies = FENrulePlies;
12113 for( i=0; i< nrCastlingRights; i++ )
12114 initialRights[i] = initial_position[CASTLING][i];
12116 yyboardindex = forwardMostMove;
12117 free(gameInfo.fen);
12118 gameInfo.fen = NULL;
12121 yyboardindex = forwardMostMove;
12122 cm = (ChessMove) Myylex();
12124 /* Handle comments interspersed among the tags */
12125 while (cm == Comment) {
12127 if (appData.debugMode)
12128 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12130 AppendComment(currentMove, p, FALSE);
12131 yyboardindex = forwardMostMove;
12132 cm = (ChessMove) Myylex();
12136 /* don't rely on existence of Event tag since if game was
12137 * pasted from clipboard the Event tag may not exist
12139 if (numPGNTags > 0){
12141 if (gameInfo.variant == VariantNormal) {
12142 VariantClass v = StringToVariant(gameInfo.event);
12143 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12144 if(v < VariantShogi) gameInfo.variant = v;
12147 if( appData.autoDisplayTags ) {
12148 tags = PGNTags(&gameInfo);
12149 TagsPopUp(tags, CmailMsg());
12154 /* Make something up, but don't display it now */
12159 if (cm == PositionDiagram) {
12162 Board initial_position;
12164 if (appData.debugMode)
12165 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12167 if (!startedFromSetupPosition) {
12169 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12170 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12181 initial_position[i][j++] = CharToPiece(*p);
12184 while (*p == ' ' || *p == '\t' ||
12185 *p == '\n' || *p == '\r') p++;
12187 if (strncmp(p, "black", strlen("black"))==0)
12188 blackPlaysFirst = TRUE;
12190 blackPlaysFirst = FALSE;
12191 startedFromSetupPosition = TRUE;
12193 CopyBoard(boards[0], initial_position);
12194 if (blackPlaysFirst) {
12195 currentMove = forwardMostMove = backwardMostMove = 1;
12196 CopyBoard(boards[1], initial_position);
12197 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12198 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12199 timeRemaining[0][1] = whiteTimeRemaining;
12200 timeRemaining[1][1] = blackTimeRemaining;
12201 if (commentList[0] != NULL) {
12202 commentList[1] = commentList[0];
12203 commentList[0] = NULL;
12206 currentMove = forwardMostMove = backwardMostMove = 0;
12209 yyboardindex = forwardMostMove;
12210 cm = (ChessMove) Myylex();
12213 if (first.pr == NoProc) {
12214 StartChessProgram(&first);
12216 InitChessProgram(&first, FALSE);
12217 SendToProgram("force\n", &first);
12218 if (startedFromSetupPosition) {
12219 SendBoard(&first, forwardMostMove);
12220 if (appData.debugMode) {
12221 fprintf(debugFP, "Load Game\n");
12223 DisplayBothClocks();
12226 /* [HGM] server: flag to write setup moves in broadcast file as one */
12227 loadFlag = appData.suppressLoadMoves;
12229 while (cm == Comment) {
12231 if (appData.debugMode)
12232 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12234 AppendComment(currentMove, p, FALSE);
12235 yyboardindex = forwardMostMove;
12236 cm = (ChessMove) Myylex();
12239 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12240 cm == WhiteWins || cm == BlackWins ||
12241 cm == GameIsDrawn || cm == GameUnfinished) {
12242 DisplayMessage("", _("No moves in game"));
12243 if (cmailMsgLoaded) {
12244 if (appData.debugMode)
12245 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12249 DrawPosition(FALSE, boards[currentMove]);
12250 DisplayBothClocks();
12251 gameMode = EditGame;
12258 // [HGM] PV info: routine tests if comment empty
12259 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12260 DisplayComment(currentMove - 1, commentList[currentMove]);
12262 if (!matchMode && appData.timeDelay != 0)
12263 DrawPosition(FALSE, boards[currentMove]);
12265 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12266 programStats.ok_to_send = 1;
12269 /* if the first token after the PGN tags is a move
12270 * and not move number 1, retrieve it from the parser
12272 if (cm != MoveNumberOne)
12273 LoadGameOneMove(cm);
12275 /* load the remaining moves from the file */
12276 while (LoadGameOneMove(EndOfFile)) {
12277 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12278 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12281 /* rewind to the start of the game */
12282 currentMove = backwardMostMove;
12284 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12286 if (oldGameMode == AnalyzeFile ||
12287 oldGameMode == AnalyzeMode) {
12288 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12290 AnalyzeFileEvent();
12294 if (!matchMode && pos > 0) {
12295 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12297 if (matchMode || appData.timeDelay == 0) {
12299 } else if (appData.timeDelay > 0) {
12300 AutoPlayGameLoop();
12303 if (appData.debugMode)
12304 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12306 loadFlag = 0; /* [HGM] true game starts */
12310 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12312 ReloadPosition (int offset)
12314 int positionNumber = lastLoadPositionNumber + offset;
12315 if (lastLoadPositionFP == NULL) {
12316 DisplayError(_("No position has been loaded yet"), 0);
12319 if (positionNumber <= 0) {
12320 DisplayError(_("Can't back up any further"), 0);
12323 return LoadPosition(lastLoadPositionFP, positionNumber,
12324 lastLoadPositionTitle);
12327 /* Load the nth position from the given file */
12329 LoadPositionFromFile (char *filename, int n, char *title)
12334 if (strcmp(filename, "-") == 0) {
12335 return LoadPosition(stdin, n, "stdin");
12337 f = fopen(filename, "rb");
12339 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12340 DisplayError(buf, errno);
12343 return LoadPosition(f, n, title);
12348 /* Load the nth position from the given open file, and close it */
12350 LoadPosition (FILE *f, int positionNumber, char *title)
12352 char *p, line[MSG_SIZ];
12353 Board initial_position;
12354 int i, j, fenMode, pn;
12356 if (gameMode == Training )
12357 SetTrainingModeOff();
12359 if (gameMode != BeginningOfGame) {
12360 Reset(FALSE, TRUE);
12362 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12363 fclose(lastLoadPositionFP);
12365 if (positionNumber == 0) positionNumber = 1;
12366 lastLoadPositionFP = f;
12367 lastLoadPositionNumber = positionNumber;
12368 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12369 if (first.pr == NoProc && !appData.noChessProgram) {
12370 StartChessProgram(&first);
12371 InitChessProgram(&first, FALSE);
12373 pn = positionNumber;
12374 if (positionNumber < 0) {
12375 /* Negative position number means to seek to that byte offset */
12376 if (fseek(f, -positionNumber, 0) == -1) {
12377 DisplayError(_("Can't seek on position file"), 0);
12382 if (fseek(f, 0, 0) == -1) {
12383 if (f == lastLoadPositionFP ?
12384 positionNumber == lastLoadPositionNumber + 1 :
12385 positionNumber == 1) {
12388 DisplayError(_("Can't seek on position file"), 0);
12393 /* See if this file is FEN or old-style xboard */
12394 if (fgets(line, MSG_SIZ, f) == NULL) {
12395 DisplayError(_("Position not found in file"), 0);
12398 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12399 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12402 if (fenMode || line[0] == '#') pn--;
12404 /* skip positions before number pn */
12405 if (fgets(line, MSG_SIZ, f) == NULL) {
12407 DisplayError(_("Position not found in file"), 0);
12410 if (fenMode || line[0] == '#') pn--;
12415 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12416 DisplayError(_("Bad FEN position in file"), 0);
12420 (void) fgets(line, MSG_SIZ, f);
12421 (void) fgets(line, MSG_SIZ, f);
12423 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12424 (void) fgets(line, MSG_SIZ, f);
12425 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12428 initial_position[i][j++] = CharToPiece(*p);
12432 blackPlaysFirst = FALSE;
12434 (void) fgets(line, MSG_SIZ, f);
12435 if (strncmp(line, "black", strlen("black"))==0)
12436 blackPlaysFirst = TRUE;
12439 startedFromSetupPosition = TRUE;
12441 CopyBoard(boards[0], initial_position);
12442 if (blackPlaysFirst) {
12443 currentMove = forwardMostMove = backwardMostMove = 1;
12444 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12445 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12446 CopyBoard(boards[1], initial_position);
12447 DisplayMessage("", _("Black to play"));
12449 currentMove = forwardMostMove = backwardMostMove = 0;
12450 DisplayMessage("", _("White to play"));
12452 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12453 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12454 SendToProgram("force\n", &first);
12455 SendBoard(&first, forwardMostMove);
12457 if (appData.debugMode) {
12459 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12460 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12461 fprintf(debugFP, "Load Position\n");
12464 if (positionNumber > 1) {
12465 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12466 DisplayTitle(line);
12468 DisplayTitle(title);
12470 gameMode = EditGame;
12473 timeRemaining[0][1] = whiteTimeRemaining;
12474 timeRemaining[1][1] = blackTimeRemaining;
12475 DrawPosition(FALSE, boards[currentMove]);
12482 CopyPlayerNameIntoFileName (char **dest, char *src)
12484 while (*src != NULLCHAR && *src != ',') {
12489 *(*dest)++ = *src++;
12495 DefaultFileName (char *ext)
12497 static char def[MSG_SIZ];
12500 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12502 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12504 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12506 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12513 /* Save the current game to the given file */
12515 SaveGameToFile (char *filename, int append)
12519 int result, i, t,tot=0;
12521 if (strcmp(filename, "-") == 0) {
12522 return SaveGame(stdout, 0, NULL);
12524 for(i=0; i<10; i++) { // upto 10 tries
12525 f = fopen(filename, append ? "a" : "w");
12526 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12527 if(f || errno != 13) break;
12528 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12532 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12533 DisplayError(buf, errno);
12536 safeStrCpy(buf, lastMsg, MSG_SIZ);
12537 DisplayMessage(_("Waiting for access to save file"), "");
12538 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12539 DisplayMessage(_("Saving game"), "");
12540 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12541 result = SaveGame(f, 0, NULL);
12542 DisplayMessage(buf, "");
12549 SavePart (char *str)
12551 static char buf[MSG_SIZ];
12554 p = strchr(str, ' ');
12555 if (p == NULL) return str;
12556 strncpy(buf, str, p - str);
12557 buf[p - str] = NULLCHAR;
12561 #define PGN_MAX_LINE 75
12563 #define PGN_SIDE_WHITE 0
12564 #define PGN_SIDE_BLACK 1
12567 FindFirstMoveOutOfBook (int side)
12571 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12572 int index = backwardMostMove;
12573 int has_book_hit = 0;
12575 if( (index % 2) != side ) {
12579 while( index < forwardMostMove ) {
12580 /* Check to see if engine is in book */
12581 int depth = pvInfoList[index].depth;
12582 int score = pvInfoList[index].score;
12588 else if( score == 0 && depth == 63 ) {
12589 in_book = 1; /* Zappa */
12591 else if( score == 2 && depth == 99 ) {
12592 in_book = 1; /* Abrok */
12595 has_book_hit += in_book;
12611 GetOutOfBookInfo (char * buf)
12615 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12617 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12618 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12622 if( oob[0] >= 0 || oob[1] >= 0 ) {
12623 for( i=0; i<2; i++ ) {
12627 if( i > 0 && oob[0] >= 0 ) {
12628 strcat( buf, " " );
12631 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12632 sprintf( buf+strlen(buf), "%s%.2f",
12633 pvInfoList[idx].score >= 0 ? "+" : "",
12634 pvInfoList[idx].score / 100.0 );
12640 /* Save game in PGN style and close the file */
12642 SaveGamePGN (FILE *f)
12644 int i, offset, linelen, newblock;
12647 int movelen, numlen, blank;
12648 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12650 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12652 PrintPGNTags(f, &gameInfo);
12654 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12656 if (backwardMostMove > 0 || startedFromSetupPosition) {
12657 char *fen = PositionToFEN(backwardMostMove, NULL);
12658 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12659 fprintf(f, "\n{--------------\n");
12660 PrintPosition(f, backwardMostMove);
12661 fprintf(f, "--------------}\n");
12665 /* [AS] Out of book annotation */
12666 if( appData.saveOutOfBookInfo ) {
12669 GetOutOfBookInfo( buf );
12671 if( buf[0] != '\0' ) {
12672 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12679 i = backwardMostMove;
12683 while (i < forwardMostMove) {
12684 /* Print comments preceding this move */
12685 if (commentList[i] != NULL) {
12686 if (linelen > 0) fprintf(f, "\n");
12687 fprintf(f, "%s", commentList[i]);
12692 /* Format move number */
12694 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12697 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12699 numtext[0] = NULLCHAR;
12701 numlen = strlen(numtext);
12704 /* Print move number */
12705 blank = linelen > 0 && numlen > 0;
12706 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12715 fprintf(f, "%s", numtext);
12719 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12720 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12723 blank = linelen > 0 && movelen > 0;
12724 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12733 fprintf(f, "%s", move_buffer);
12734 linelen += movelen;
12736 /* [AS] Add PV info if present */
12737 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12738 /* [HGM] add time */
12739 char buf[MSG_SIZ]; int seconds;
12741 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12747 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12750 seconds = (seconds + 4)/10; // round to full seconds
12752 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12754 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12757 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12758 pvInfoList[i].score >= 0 ? "+" : "",
12759 pvInfoList[i].score / 100.0,
12760 pvInfoList[i].depth,
12763 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12765 /* Print score/depth */
12766 blank = linelen > 0 && movelen > 0;
12767 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12776 fprintf(f, "%s", move_buffer);
12777 linelen += movelen;
12783 /* Start a new line */
12784 if (linelen > 0) fprintf(f, "\n");
12786 /* Print comments after last move */
12787 if (commentList[i] != NULL) {
12788 fprintf(f, "%s\n", commentList[i]);
12792 if (gameInfo.resultDetails != NULL &&
12793 gameInfo.resultDetails[0] != NULLCHAR) {
12794 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12795 PGNResult(gameInfo.result));
12797 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12801 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12805 /* Save game in old style and close the file */
12807 SaveGameOldStyle (FILE *f)
12812 tm = time((time_t *) NULL);
12814 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12817 if (backwardMostMove > 0 || startedFromSetupPosition) {
12818 fprintf(f, "\n[--------------\n");
12819 PrintPosition(f, backwardMostMove);
12820 fprintf(f, "--------------]\n");
12825 i = backwardMostMove;
12826 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12828 while (i < forwardMostMove) {
12829 if (commentList[i] != NULL) {
12830 fprintf(f, "[%s]\n", commentList[i]);
12833 if ((i % 2) == 1) {
12834 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12837 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12839 if (commentList[i] != NULL) {
12843 if (i >= forwardMostMove) {
12847 fprintf(f, "%s\n", parseList[i]);
12852 if (commentList[i] != NULL) {
12853 fprintf(f, "[%s]\n", commentList[i]);
12856 /* This isn't really the old style, but it's close enough */
12857 if (gameInfo.resultDetails != NULL &&
12858 gameInfo.resultDetails[0] != NULLCHAR) {
12859 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12860 gameInfo.resultDetails);
12862 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12869 /* Save the current game to open file f and close the file */
12871 SaveGame (FILE *f, int dummy, char *dummy2)
12873 if (gameMode == EditPosition) EditPositionDone(TRUE);
12874 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12875 if (appData.oldSaveStyle)
12876 return SaveGameOldStyle(f);
12878 return SaveGamePGN(f);
12881 /* Save the current position to the given file */
12883 SavePositionToFile (char *filename)
12888 if (strcmp(filename, "-") == 0) {
12889 return SavePosition(stdout, 0, NULL);
12891 f = fopen(filename, "a");
12893 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12894 DisplayError(buf, errno);
12897 safeStrCpy(buf, lastMsg, MSG_SIZ);
12898 DisplayMessage(_("Waiting for access to save file"), "");
12899 flock(fileno(f), LOCK_EX); // [HGM] lock
12900 DisplayMessage(_("Saving position"), "");
12901 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12902 SavePosition(f, 0, NULL);
12903 DisplayMessage(buf, "");
12909 /* Save the current position to the given open file and close the file */
12911 SavePosition (FILE *f, int dummy, char *dummy2)
12916 if (gameMode == EditPosition) EditPositionDone(TRUE);
12917 if (appData.oldSaveStyle) {
12918 tm = time((time_t *) NULL);
12920 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12922 fprintf(f, "[--------------\n");
12923 PrintPosition(f, currentMove);
12924 fprintf(f, "--------------]\n");
12926 fen = PositionToFEN(currentMove, NULL);
12927 fprintf(f, "%s\n", fen);
12935 ReloadCmailMsgEvent (int unregister)
12938 static char *inFilename = NULL;
12939 static char *outFilename;
12941 struct stat inbuf, outbuf;
12944 /* Any registered moves are unregistered if unregister is set, */
12945 /* i.e. invoked by the signal handler */
12947 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12948 cmailMoveRegistered[i] = FALSE;
12949 if (cmailCommentList[i] != NULL) {
12950 free(cmailCommentList[i]);
12951 cmailCommentList[i] = NULL;
12954 nCmailMovesRegistered = 0;
12957 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12958 cmailResult[i] = CMAIL_NOT_RESULT;
12962 if (inFilename == NULL) {
12963 /* Because the filenames are static they only get malloced once */
12964 /* and they never get freed */
12965 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12966 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12968 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12969 sprintf(outFilename, "%s.out", appData.cmailGameName);
12972 status = stat(outFilename, &outbuf);
12974 cmailMailedMove = FALSE;
12976 status = stat(inFilename, &inbuf);
12977 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12980 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12981 counts the games, notes how each one terminated, etc.
12983 It would be nice to remove this kludge and instead gather all
12984 the information while building the game list. (And to keep it
12985 in the game list nodes instead of having a bunch of fixed-size
12986 parallel arrays.) Note this will require getting each game's
12987 termination from the PGN tags, as the game list builder does
12988 not process the game moves. --mann
12990 cmailMsgLoaded = TRUE;
12991 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12993 /* Load first game in the file or popup game menu */
12994 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12996 #endif /* !WIN32 */
13004 char string[MSG_SIZ];
13006 if ( cmailMailedMove
13007 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13008 return TRUE; /* Allow free viewing */
13011 /* Unregister move to ensure that we don't leave RegisterMove */
13012 /* with the move registered when the conditions for registering no */
13014 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13015 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13016 nCmailMovesRegistered --;
13018 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13020 free(cmailCommentList[lastLoadGameNumber - 1]);
13021 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13025 if (cmailOldMove == -1) {
13026 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13030 if (currentMove > cmailOldMove + 1) {
13031 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13035 if (currentMove < cmailOldMove) {
13036 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13040 if (forwardMostMove > currentMove) {
13041 /* Silently truncate extra moves */
13045 if ( (currentMove == cmailOldMove + 1)
13046 || ( (currentMove == cmailOldMove)
13047 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13048 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13049 if (gameInfo.result != GameUnfinished) {
13050 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13053 if (commentList[currentMove] != NULL) {
13054 cmailCommentList[lastLoadGameNumber - 1]
13055 = StrSave(commentList[currentMove]);
13057 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13059 if (appData.debugMode)
13060 fprintf(debugFP, "Saving %s for game %d\n",
13061 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13063 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13065 f = fopen(string, "w");
13066 if (appData.oldSaveStyle) {
13067 SaveGameOldStyle(f); /* also closes the file */
13069 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13070 f = fopen(string, "w");
13071 SavePosition(f, 0, NULL); /* also closes the file */
13073 fprintf(f, "{--------------\n");
13074 PrintPosition(f, currentMove);
13075 fprintf(f, "--------------}\n\n");
13077 SaveGame(f, 0, NULL); /* also closes the file*/
13080 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13081 nCmailMovesRegistered ++;
13082 } else if (nCmailGames == 1) {
13083 DisplayError(_("You have not made a move yet"), 0);
13094 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13095 FILE *commandOutput;
13096 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13097 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13103 if (! cmailMsgLoaded) {
13104 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13108 if (nCmailGames == nCmailResults) {
13109 DisplayError(_("No unfinished games"), 0);
13113 #if CMAIL_PROHIBIT_REMAIL
13114 if (cmailMailedMove) {
13115 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);
13116 DisplayError(msg, 0);
13121 if (! (cmailMailedMove || RegisterMove())) return;
13123 if ( cmailMailedMove
13124 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13125 snprintf(string, MSG_SIZ, partCommandString,
13126 appData.debugMode ? " -v" : "", appData.cmailGameName);
13127 commandOutput = popen(string, "r");
13129 if (commandOutput == NULL) {
13130 DisplayError(_("Failed to invoke cmail"), 0);
13132 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13133 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13135 if (nBuffers > 1) {
13136 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13137 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13138 nBytes = MSG_SIZ - 1;
13140 (void) memcpy(msg, buffer, nBytes);
13142 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13144 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13145 cmailMailedMove = TRUE; /* Prevent >1 moves */
13148 for (i = 0; i < nCmailGames; i ++) {
13149 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13154 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13156 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13158 appData.cmailGameName,
13160 LoadGameFromFile(buffer, 1, buffer, FALSE);
13161 cmailMsgLoaded = FALSE;
13165 DisplayInformation(msg);
13166 pclose(commandOutput);
13169 if ((*cmailMsg) != '\0') {
13170 DisplayInformation(cmailMsg);
13175 #endif /* !WIN32 */
13184 int prependComma = 0;
13186 char string[MSG_SIZ]; /* Space for game-list */
13189 if (!cmailMsgLoaded) return "";
13191 if (cmailMailedMove) {
13192 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13194 /* Create a list of games left */
13195 snprintf(string, MSG_SIZ, "[");
13196 for (i = 0; i < nCmailGames; i ++) {
13197 if (! ( cmailMoveRegistered[i]
13198 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13199 if (prependComma) {
13200 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13202 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13206 strcat(string, number);
13209 strcat(string, "]");
13211 if (nCmailMovesRegistered + nCmailResults == 0) {
13212 switch (nCmailGames) {
13214 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13218 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13222 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13227 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13229 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13234 if (nCmailResults == nCmailGames) {
13235 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13237 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13242 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13254 if (gameMode == Training)
13255 SetTrainingModeOff();
13258 cmailMsgLoaded = FALSE;
13259 if (appData.icsActive) {
13260 SendToICS(ics_prefix);
13261 SendToICS("refresh\n");
13266 ExitEvent (int status)
13270 /* Give up on clean exit */
13274 /* Keep trying for clean exit */
13278 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13280 if (telnetISR != NULL) {
13281 RemoveInputSource(telnetISR);
13283 if (icsPR != NoProc) {
13284 DestroyChildProcess(icsPR, TRUE);
13287 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13288 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13290 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13291 /* make sure this other one finishes before killing it! */
13292 if(endingGame) { int count = 0;
13293 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13294 while(endingGame && count++ < 10) DoSleep(1);
13295 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13298 /* Kill off chess programs */
13299 if (first.pr != NoProc) {
13302 DoSleep( appData.delayBeforeQuit );
13303 SendToProgram("quit\n", &first);
13304 DoSleep( appData.delayAfterQuit );
13305 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13307 if (second.pr != NoProc) {
13308 DoSleep( appData.delayBeforeQuit );
13309 SendToProgram("quit\n", &second);
13310 DoSleep( appData.delayAfterQuit );
13311 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13313 if (first.isr != NULL) {
13314 RemoveInputSource(first.isr);
13316 if (second.isr != NULL) {
13317 RemoveInputSource(second.isr);
13320 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13321 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13323 ShutDownFrontEnd();
13330 if (appData.debugMode)
13331 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13335 if (gameMode == MachinePlaysWhite ||
13336 gameMode == MachinePlaysBlack) {
13339 DisplayBothClocks();
13341 if (gameMode == PlayFromGameFile) {
13342 if (appData.timeDelay >= 0)
13343 AutoPlayGameLoop();
13344 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13345 Reset(FALSE, TRUE);
13346 SendToICS(ics_prefix);
13347 SendToICS("refresh\n");
13348 } else if (currentMove < forwardMostMove) {
13349 ForwardInner(forwardMostMove);
13351 pauseExamInvalid = FALSE;
13353 switch (gameMode) {
13357 pauseExamForwardMostMove = forwardMostMove;
13358 pauseExamInvalid = FALSE;
13361 case IcsPlayingWhite:
13362 case IcsPlayingBlack:
13366 case PlayFromGameFile:
13367 (void) StopLoadGameTimer();
13371 case BeginningOfGame:
13372 if (appData.icsActive) return;
13373 /* else fall through */
13374 case MachinePlaysWhite:
13375 case MachinePlaysBlack:
13376 case TwoMachinesPlay:
13377 if (forwardMostMove == 0)
13378 return; /* don't pause if no one has moved */
13379 if ((gameMode == MachinePlaysWhite &&
13380 !WhiteOnMove(forwardMostMove)) ||
13381 (gameMode == MachinePlaysBlack &&
13382 WhiteOnMove(forwardMostMove))) {
13394 EditCommentEvent ()
13396 char title[MSG_SIZ];
13398 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13399 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13401 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13402 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13403 parseList[currentMove - 1]);
13406 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13413 char *tags = PGNTags(&gameInfo);
13415 EditTagsPopUp(tags, NULL);
13422 if(second.analyzing) {
13423 SendToProgram("exit\n", &second);
13424 second.analyzing = FALSE;
13426 if (second.pr == NoProc) StartChessProgram(&second);
13427 InitChessProgram(&second, FALSE);
13428 FeedMovesToProgram(&second, currentMove);
13430 SendToProgram("analyze\n", &second);
13431 second.analyzing = TRUE;
13436 AnalyzeModeEvent ()
13438 if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13439 if (appData.noChessProgram || gameMode == AnalyzeMode)
13442 if (gameMode != AnalyzeFile) {
13443 if (!appData.icsEngineAnalyze) {
13445 if (gameMode != EditGame) return;
13447 ResurrectChessProgram();
13448 SendToProgram("analyze\n", &first);
13449 first.analyzing = TRUE;
13450 /*first.maybeThinking = TRUE;*/
13451 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13452 EngineOutputPopUp();
13454 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13459 StartAnalysisClock();
13460 GetTimeMark(&lastNodeCountTime);
13465 AnalyzeFileEvent ()
13467 if (appData.noChessProgram || gameMode == AnalyzeFile)
13470 if (gameMode != AnalyzeMode) {
13472 if (gameMode != EditGame) return;
13473 ResurrectChessProgram();
13474 SendToProgram("analyze\n", &first);
13475 first.analyzing = TRUE;
13476 /*first.maybeThinking = TRUE;*/
13477 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13478 EngineOutputPopUp();
13480 gameMode = AnalyzeFile;
13485 StartAnalysisClock();
13486 GetTimeMark(&lastNodeCountTime);
13488 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13492 MachineWhiteEvent ()
13495 char *bookHit = NULL;
13497 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13501 if (gameMode == PlayFromGameFile ||
13502 gameMode == TwoMachinesPlay ||
13503 gameMode == Training ||
13504 gameMode == AnalyzeMode ||
13505 gameMode == EndOfGame)
13508 if (gameMode == EditPosition)
13509 EditPositionDone(TRUE);
13511 if (!WhiteOnMove(currentMove)) {
13512 DisplayError(_("It is not White's turn"), 0);
13516 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13519 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13520 gameMode == AnalyzeFile)
13523 ResurrectChessProgram(); /* in case it isn't running */
13524 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13525 gameMode = MachinePlaysWhite;
13528 gameMode = MachinePlaysWhite;
13532 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13534 if (first.sendName) {
13535 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13536 SendToProgram(buf, &first);
13538 if (first.sendTime) {
13539 if (first.useColors) {
13540 SendToProgram("black\n", &first); /*gnu kludge*/
13542 SendTimeRemaining(&first, TRUE);
13544 if (first.useColors) {
13545 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13547 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13548 SetMachineThinkingEnables();
13549 first.maybeThinking = TRUE;
13553 if (appData.autoFlipView && !flipView) {
13554 flipView = !flipView;
13555 DrawPosition(FALSE, NULL);
13556 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13559 if(bookHit) { // [HGM] book: simulate book reply
13560 static char bookMove[MSG_SIZ]; // a bit generous?
13562 programStats.nodes = programStats.depth = programStats.time =
13563 programStats.score = programStats.got_only_move = 0;
13564 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13566 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13567 strcat(bookMove, bookHit);
13568 HandleMachineMove(bookMove, &first);
13573 MachineBlackEvent ()
13576 char *bookHit = NULL;
13578 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13582 if (gameMode == PlayFromGameFile ||
13583 gameMode == TwoMachinesPlay ||
13584 gameMode == Training ||
13585 gameMode == AnalyzeMode ||
13586 gameMode == EndOfGame)
13589 if (gameMode == EditPosition)
13590 EditPositionDone(TRUE);
13592 if (WhiteOnMove(currentMove)) {
13593 DisplayError(_("It is not Black's turn"), 0);
13597 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13600 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13601 gameMode == AnalyzeFile)
13604 ResurrectChessProgram(); /* in case it isn't running */
13605 gameMode = MachinePlaysBlack;
13609 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13611 if (first.sendName) {
13612 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13613 SendToProgram(buf, &first);
13615 if (first.sendTime) {
13616 if (first.useColors) {
13617 SendToProgram("white\n", &first); /*gnu kludge*/
13619 SendTimeRemaining(&first, FALSE);
13621 if (first.useColors) {
13622 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13624 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13625 SetMachineThinkingEnables();
13626 first.maybeThinking = TRUE;
13629 if (appData.autoFlipView && flipView) {
13630 flipView = !flipView;
13631 DrawPosition(FALSE, NULL);
13632 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13634 if(bookHit) { // [HGM] book: simulate book reply
13635 static char bookMove[MSG_SIZ]; // a bit generous?
13637 programStats.nodes = programStats.depth = programStats.time =
13638 programStats.score = programStats.got_only_move = 0;
13639 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13641 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13642 strcat(bookMove, bookHit);
13643 HandleMachineMove(bookMove, &first);
13649 DisplayTwoMachinesTitle ()
13652 if (appData.matchGames > 0) {
13653 if(appData.tourneyFile[0]) {
13654 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13655 gameInfo.white, _("vs."), gameInfo.black,
13656 nextGame+1, appData.matchGames+1,
13657 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13659 if (first.twoMachinesColor[0] == 'w') {
13660 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13661 gameInfo.white, _("vs."), gameInfo.black,
13662 first.matchWins, second.matchWins,
13663 matchGame - 1 - (first.matchWins + second.matchWins));
13665 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13666 gameInfo.white, _("vs."), gameInfo.black,
13667 second.matchWins, first.matchWins,
13668 matchGame - 1 - (first.matchWins + second.matchWins));
13671 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13677 SettingsMenuIfReady ()
13679 if (second.lastPing != second.lastPong) {
13680 DisplayMessage("", _("Waiting for second chess program"));
13681 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13685 DisplayMessage("", "");
13686 SettingsPopUp(&second);
13690 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13693 if (cps->pr == NoProc) {
13694 StartChessProgram(cps);
13695 if (cps->protocolVersion == 1) {
13698 /* kludge: allow timeout for initial "feature" command */
13700 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13701 DisplayMessage("", buf);
13702 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13710 TwoMachinesEvent P((void))
13714 ChessProgramState *onmove;
13715 char *bookHit = NULL;
13716 static int stalling = 0;
13720 if (appData.noChessProgram) return;
13722 switch (gameMode) {
13723 case TwoMachinesPlay:
13725 case MachinePlaysWhite:
13726 case MachinePlaysBlack:
13727 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13728 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13732 case BeginningOfGame:
13733 case PlayFromGameFile:
13736 if (gameMode != EditGame) return;
13739 EditPositionDone(TRUE);
13750 // forwardMostMove = currentMove;
13751 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13753 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13755 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13756 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13757 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13761 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13762 DisplayError("second engine does not play this", 0);
13767 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13768 SendToProgram("force\n", &second);
13770 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13773 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13774 if(appData.matchPause>10000 || appData.matchPause<10)
13775 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13776 wait = SubtractTimeMarks(&now, &pauseStart);
13777 if(wait < appData.matchPause) {
13778 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13781 // we are now committed to starting the game
13783 DisplayMessage("", "");
13784 if (startedFromSetupPosition) {
13785 SendBoard(&second, backwardMostMove);
13786 if (appData.debugMode) {
13787 fprintf(debugFP, "Two Machines\n");
13790 for (i = backwardMostMove; i < forwardMostMove; i++) {
13791 SendMoveToProgram(i, &second);
13794 gameMode = TwoMachinesPlay;
13796 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13798 DisplayTwoMachinesTitle();
13800 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13805 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13806 SendToProgram(first.computerString, &first);
13807 if (first.sendName) {
13808 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13809 SendToProgram(buf, &first);
13811 SendToProgram(second.computerString, &second);
13812 if (second.sendName) {
13813 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13814 SendToProgram(buf, &second);
13818 if (!first.sendTime || !second.sendTime) {
13819 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13820 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13822 if (onmove->sendTime) {
13823 if (onmove->useColors) {
13824 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13826 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13828 if (onmove->useColors) {
13829 SendToProgram(onmove->twoMachinesColor, onmove);
13831 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13832 // SendToProgram("go\n", onmove);
13833 onmove->maybeThinking = TRUE;
13834 SetMachineThinkingEnables();
13838 if(bookHit) { // [HGM] book: simulate book reply
13839 static char bookMove[MSG_SIZ]; // a bit generous?
13841 programStats.nodes = programStats.depth = programStats.time =
13842 programStats.score = programStats.got_only_move = 0;
13843 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13845 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13846 strcat(bookMove, bookHit);
13847 savedMessage = bookMove; // args for deferred call
13848 savedState = onmove;
13849 ScheduleDelayedEvent(DeferredBookMove, 1);
13856 if (gameMode == Training) {
13857 SetTrainingModeOff();
13858 gameMode = PlayFromGameFile;
13859 DisplayMessage("", _("Training mode off"));
13861 gameMode = Training;
13862 animateTraining = appData.animate;
13864 /* make sure we are not already at the end of the game */
13865 if (currentMove < forwardMostMove) {
13866 SetTrainingModeOn();
13867 DisplayMessage("", _("Training mode on"));
13869 gameMode = PlayFromGameFile;
13870 DisplayError(_("Already at end of game"), 0);
13879 if (!appData.icsActive) return;
13880 switch (gameMode) {
13881 case IcsPlayingWhite:
13882 case IcsPlayingBlack:
13885 case BeginningOfGame:
13893 EditPositionDone(TRUE);
13906 gameMode = IcsIdle;
13916 switch (gameMode) {
13918 SetTrainingModeOff();
13920 case MachinePlaysWhite:
13921 case MachinePlaysBlack:
13922 case BeginningOfGame:
13923 SendToProgram("force\n", &first);
13924 SetUserThinkingEnables();
13926 case PlayFromGameFile:
13927 (void) StopLoadGameTimer();
13928 if (gameFileFP != NULL) {
13933 EditPositionDone(TRUE);
13938 SendToProgram("force\n", &first);
13940 case TwoMachinesPlay:
13941 GameEnds(EndOfFile, NULL, GE_PLAYER);
13942 ResurrectChessProgram();
13943 SetUserThinkingEnables();
13946 ResurrectChessProgram();
13948 case IcsPlayingBlack:
13949 case IcsPlayingWhite:
13950 DisplayError(_("Warning: You are still playing a game"), 0);
13953 DisplayError(_("Warning: You are still observing a game"), 0);
13956 DisplayError(_("Warning: You are still examining a game"), 0);
13967 first.offeredDraw = second.offeredDraw = 0;
13969 if (gameMode == PlayFromGameFile) {
13970 whiteTimeRemaining = timeRemaining[0][currentMove];
13971 blackTimeRemaining = timeRemaining[1][currentMove];
13975 if (gameMode == MachinePlaysWhite ||
13976 gameMode == MachinePlaysBlack ||
13977 gameMode == TwoMachinesPlay ||
13978 gameMode == EndOfGame) {
13979 i = forwardMostMove;
13980 while (i > currentMove) {
13981 SendToProgram("undo\n", &first);
13984 if(!adjustedClock) {
13985 whiteTimeRemaining = timeRemaining[0][currentMove];
13986 blackTimeRemaining = timeRemaining[1][currentMove];
13987 DisplayBothClocks();
13989 if (whiteFlag || blackFlag) {
13990 whiteFlag = blackFlag = 0;
13995 gameMode = EditGame;
14002 EditPositionEvent ()
14004 if (gameMode == EditPosition) {
14010 if (gameMode != EditGame) return;
14012 gameMode = EditPosition;
14015 if (currentMove > 0)
14016 CopyBoard(boards[0], boards[currentMove]);
14018 blackPlaysFirst = !WhiteOnMove(currentMove);
14020 currentMove = forwardMostMove = backwardMostMove = 0;
14021 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14023 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14029 /* [DM] icsEngineAnalyze - possible call from other functions */
14030 if (appData.icsEngineAnalyze) {
14031 appData.icsEngineAnalyze = FALSE;
14033 DisplayMessage("",_("Close ICS engine analyze..."));
14035 if (first.analysisSupport && first.analyzing) {
14036 SendToBoth("exit\n");
14037 first.analyzing = second.analyzing = FALSE;
14039 thinkOutput[0] = NULLCHAR;
14043 EditPositionDone (Boolean fakeRights)
14045 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14047 startedFromSetupPosition = TRUE;
14048 InitChessProgram(&first, FALSE);
14049 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14050 boards[0][EP_STATUS] = EP_NONE;
14051 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14052 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14053 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14054 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14055 } else boards[0][CASTLING][2] = NoRights;
14056 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14057 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14058 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14059 } else boards[0][CASTLING][5] = NoRights;
14060 if(gameInfo.variant == VariantSChess) {
14062 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14063 boards[0][VIRGIN][i] = 0;
14064 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14065 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14069 SendToProgram("force\n", &first);
14070 if (blackPlaysFirst) {
14071 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14072 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14073 currentMove = forwardMostMove = backwardMostMove = 1;
14074 CopyBoard(boards[1], boards[0]);
14076 currentMove = forwardMostMove = backwardMostMove = 0;
14078 SendBoard(&first, forwardMostMove);
14079 if (appData.debugMode) {
14080 fprintf(debugFP, "EditPosDone\n");
14083 DisplayMessage("", "");
14084 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14085 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14086 gameMode = EditGame;
14088 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14089 ClearHighlights(); /* [AS] */
14092 /* Pause for `ms' milliseconds */
14093 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14095 TimeDelay (long ms)
14102 } while (SubtractTimeMarks(&m2, &m1) < ms);
14105 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14107 SendMultiLineToICS (char *buf)
14109 char temp[MSG_SIZ+1], *p;
14116 strncpy(temp, buf, len);
14121 if (*p == '\n' || *p == '\r')
14126 strcat(temp, "\n");
14128 SendToPlayer(temp, strlen(temp));
14132 SetWhiteToPlayEvent ()
14134 if (gameMode == EditPosition) {
14135 blackPlaysFirst = FALSE;
14136 DisplayBothClocks(); /* works because currentMove is 0 */
14137 } else if (gameMode == IcsExamining) {
14138 SendToICS(ics_prefix);
14139 SendToICS("tomove white\n");
14144 SetBlackToPlayEvent ()
14146 if (gameMode == EditPosition) {
14147 blackPlaysFirst = TRUE;
14148 currentMove = 1; /* kludge */
14149 DisplayBothClocks();
14151 } else if (gameMode == IcsExamining) {
14152 SendToICS(ics_prefix);
14153 SendToICS("tomove black\n");
14158 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14161 ChessSquare piece = boards[0][y][x];
14163 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14165 switch (selection) {
14167 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14168 SendToICS(ics_prefix);
14169 SendToICS("bsetup clear\n");
14170 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14171 SendToICS(ics_prefix);
14172 SendToICS("clearboard\n");
14174 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14175 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14176 for (y = 0; y < BOARD_HEIGHT; y++) {
14177 if (gameMode == IcsExamining) {
14178 if (boards[currentMove][y][x] != EmptySquare) {
14179 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14184 boards[0][y][x] = p;
14189 if (gameMode == EditPosition) {
14190 DrawPosition(FALSE, boards[0]);
14195 SetWhiteToPlayEvent();
14199 SetBlackToPlayEvent();
14203 if (gameMode == IcsExamining) {
14204 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14205 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14208 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14209 if(x == BOARD_LEFT-2) {
14210 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14211 boards[0][y][1] = 0;
14213 if(x == BOARD_RGHT+1) {
14214 if(y >= gameInfo.holdingsSize) break;
14215 boards[0][y][BOARD_WIDTH-2] = 0;
14218 boards[0][y][x] = EmptySquare;
14219 DrawPosition(FALSE, boards[0]);
14224 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14225 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14226 selection = (ChessSquare) (PROMOTED piece);
14227 } else if(piece == EmptySquare) selection = WhiteSilver;
14228 else selection = (ChessSquare)((int)piece - 1);
14232 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14233 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14234 selection = (ChessSquare) (DEMOTED piece);
14235 } else if(piece == EmptySquare) selection = BlackSilver;
14236 else selection = (ChessSquare)((int)piece + 1);
14241 if(gameInfo.variant == VariantShatranj ||
14242 gameInfo.variant == VariantXiangqi ||
14243 gameInfo.variant == VariantCourier ||
14244 gameInfo.variant == VariantMakruk )
14245 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14250 if(gameInfo.variant == VariantXiangqi)
14251 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14252 if(gameInfo.variant == VariantKnightmate)
14253 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14256 if (gameMode == IcsExamining) {
14257 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14258 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14259 PieceToChar(selection), AAA + x, ONE + y);
14262 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14264 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14265 n = PieceToNumber(selection - BlackPawn);
14266 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14267 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14268 boards[0][BOARD_HEIGHT-1-n][1]++;
14270 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14271 n = PieceToNumber(selection);
14272 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14273 boards[0][n][BOARD_WIDTH-1] = selection;
14274 boards[0][n][BOARD_WIDTH-2]++;
14277 boards[0][y][x] = selection;
14278 DrawPosition(TRUE, boards[0]);
14280 fromX = fromY = -1;
14288 DropMenuEvent (ChessSquare selection, int x, int y)
14290 ChessMove moveType;
14292 switch (gameMode) {
14293 case IcsPlayingWhite:
14294 case MachinePlaysBlack:
14295 if (!WhiteOnMove(currentMove)) {
14296 DisplayMoveError(_("It is Black's turn"));
14299 moveType = WhiteDrop;
14301 case IcsPlayingBlack:
14302 case MachinePlaysWhite:
14303 if (WhiteOnMove(currentMove)) {
14304 DisplayMoveError(_("It is White's turn"));
14307 moveType = BlackDrop;
14310 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14316 if (moveType == BlackDrop && selection < BlackPawn) {
14317 selection = (ChessSquare) ((int) selection
14318 + (int) BlackPawn - (int) WhitePawn);
14320 if (boards[currentMove][y][x] != EmptySquare) {
14321 DisplayMoveError(_("That square is occupied"));
14325 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14331 /* Accept a pending offer of any kind from opponent */
14333 if (appData.icsActive) {
14334 SendToICS(ics_prefix);
14335 SendToICS("accept\n");
14336 } else if (cmailMsgLoaded) {
14337 if (currentMove == cmailOldMove &&
14338 commentList[cmailOldMove] != NULL &&
14339 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14340 "Black offers a draw" : "White offers a draw")) {
14342 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14343 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14345 DisplayError(_("There is no pending offer on this move"), 0);
14346 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14349 /* Not used for offers from chess program */
14356 /* Decline a pending offer of any kind from opponent */
14358 if (appData.icsActive) {
14359 SendToICS(ics_prefix);
14360 SendToICS("decline\n");
14361 } else if (cmailMsgLoaded) {
14362 if (currentMove == cmailOldMove &&
14363 commentList[cmailOldMove] != NULL &&
14364 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14365 "Black offers a draw" : "White offers a draw")) {
14367 AppendComment(cmailOldMove, "Draw declined", TRUE);
14368 DisplayComment(cmailOldMove - 1, "Draw declined");
14371 DisplayError(_("There is no pending offer on this move"), 0);
14374 /* Not used for offers from chess program */
14381 /* Issue ICS rematch command */
14382 if (appData.icsActive) {
14383 SendToICS(ics_prefix);
14384 SendToICS("rematch\n");
14391 /* Call your opponent's flag (claim a win on time) */
14392 if (appData.icsActive) {
14393 SendToICS(ics_prefix);
14394 SendToICS("flag\n");
14396 switch (gameMode) {
14399 case MachinePlaysWhite:
14402 GameEnds(GameIsDrawn, "Both players ran out of time",
14405 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14407 DisplayError(_("Your opponent is not out of time"), 0);
14410 case MachinePlaysBlack:
14413 GameEnds(GameIsDrawn, "Both players ran out of time",
14416 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14418 DisplayError(_("Your opponent is not out of time"), 0);
14426 ClockClick (int which)
14427 { // [HGM] code moved to back-end from winboard.c
14428 if(which) { // black clock
14429 if (gameMode == EditPosition || gameMode == IcsExamining) {
14430 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14431 SetBlackToPlayEvent();
14432 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14433 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14434 } else if (shiftKey) {
14435 AdjustClock(which, -1);
14436 } else if (gameMode == IcsPlayingWhite ||
14437 gameMode == MachinePlaysBlack) {
14440 } else { // white clock
14441 if (gameMode == EditPosition || gameMode == IcsExamining) {
14442 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14443 SetWhiteToPlayEvent();
14444 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14445 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14446 } else if (shiftKey) {
14447 AdjustClock(which, -1);
14448 } else if (gameMode == IcsPlayingBlack ||
14449 gameMode == MachinePlaysWhite) {
14458 /* Offer draw or accept pending draw offer from opponent */
14460 if (appData.icsActive) {
14461 /* Note: tournament rules require draw offers to be
14462 made after you make your move but before you punch
14463 your clock. Currently ICS doesn't let you do that;
14464 instead, you immediately punch your clock after making
14465 a move, but you can offer a draw at any time. */
14467 SendToICS(ics_prefix);
14468 SendToICS("draw\n");
14469 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14470 } else if (cmailMsgLoaded) {
14471 if (currentMove == cmailOldMove &&
14472 commentList[cmailOldMove] != NULL &&
14473 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14474 "Black offers a draw" : "White offers a draw")) {
14475 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14476 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14477 } else if (currentMove == cmailOldMove + 1) {
14478 char *offer = WhiteOnMove(cmailOldMove) ?
14479 "White offers a draw" : "Black offers a draw";
14480 AppendComment(currentMove, offer, TRUE);
14481 DisplayComment(currentMove - 1, offer);
14482 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14484 DisplayError(_("You must make your move before offering a draw"), 0);
14485 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14487 } else if (first.offeredDraw) {
14488 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14490 if (first.sendDrawOffers) {
14491 SendToProgram("draw\n", &first);
14492 userOfferedDraw = TRUE;
14500 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14502 if (appData.icsActive) {
14503 SendToICS(ics_prefix);
14504 SendToICS("adjourn\n");
14506 /* Currently GNU Chess doesn't offer or accept Adjourns */
14514 /* Offer Abort or accept pending Abort offer from opponent */
14516 if (appData.icsActive) {
14517 SendToICS(ics_prefix);
14518 SendToICS("abort\n");
14520 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14527 /* Resign. You can do this even if it's not your turn. */
14529 if (appData.icsActive) {
14530 SendToICS(ics_prefix);
14531 SendToICS("resign\n");
14533 switch (gameMode) {
14534 case MachinePlaysWhite:
14535 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14537 case MachinePlaysBlack:
14538 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14541 if (cmailMsgLoaded) {
14543 if (WhiteOnMove(cmailOldMove)) {
14544 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14546 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14548 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14559 StopObservingEvent ()
14561 /* Stop observing current games */
14562 SendToICS(ics_prefix);
14563 SendToICS("unobserve\n");
14567 StopExaminingEvent ()
14569 /* Stop observing current game */
14570 SendToICS(ics_prefix);
14571 SendToICS("unexamine\n");
14575 ForwardInner (int target)
14577 int limit; int oldSeekGraphUp = seekGraphUp;
14579 if (appData.debugMode)
14580 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14581 target, currentMove, forwardMostMove);
14583 if (gameMode == EditPosition)
14586 seekGraphUp = FALSE;
14587 MarkTargetSquares(1);
14589 if (gameMode == PlayFromGameFile && !pausing)
14592 if (gameMode == IcsExamining && pausing)
14593 limit = pauseExamForwardMostMove;
14595 limit = forwardMostMove;
14597 if (target > limit) target = limit;
14599 if (target > 0 && moveList[target - 1][0]) {
14600 int fromX, fromY, toX, toY;
14601 toX = moveList[target - 1][2] - AAA;
14602 toY = moveList[target - 1][3] - ONE;
14603 if (moveList[target - 1][1] == '@') {
14604 if (appData.highlightLastMove) {
14605 SetHighlights(-1, -1, toX, toY);
14608 fromX = moveList[target - 1][0] - AAA;
14609 fromY = moveList[target - 1][1] - ONE;
14610 if (target == currentMove + 1) {
14611 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14613 if (appData.highlightLastMove) {
14614 SetHighlights(fromX, fromY, toX, toY);
14618 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14619 gameMode == Training || gameMode == PlayFromGameFile ||
14620 gameMode == AnalyzeFile) {
14621 while (currentMove < target) {
14622 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14623 SendMoveToProgram(currentMove++, &first);
14626 currentMove = target;
14629 if (gameMode == EditGame || gameMode == EndOfGame) {
14630 whiteTimeRemaining = timeRemaining[0][currentMove];
14631 blackTimeRemaining = timeRemaining[1][currentMove];
14633 DisplayBothClocks();
14634 DisplayMove(currentMove - 1);
14635 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14636 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14637 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14638 DisplayComment(currentMove - 1, commentList[currentMove]);
14640 ClearMap(); // [HGM] exclude: invalidate map
14647 if (gameMode == IcsExamining && !pausing) {
14648 SendToICS(ics_prefix);
14649 SendToICS("forward\n");
14651 ForwardInner(currentMove + 1);
14658 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14659 /* to optimze, we temporarily turn off analysis mode while we feed
14660 * the remaining moves to the engine. Otherwise we get analysis output
14663 if (first.analysisSupport) {
14664 SendToProgram("exit\nforce\n", &first);
14665 first.analyzing = FALSE;
14669 if (gameMode == IcsExamining && !pausing) {
14670 SendToICS(ics_prefix);
14671 SendToICS("forward 999999\n");
14673 ForwardInner(forwardMostMove);
14676 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14677 /* we have fed all the moves, so reactivate analysis mode */
14678 SendToProgram("analyze\n", &first);
14679 first.analyzing = TRUE;
14680 /*first.maybeThinking = TRUE;*/
14681 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14686 BackwardInner (int target)
14688 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14690 if (appData.debugMode)
14691 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14692 target, currentMove, forwardMostMove);
14694 if (gameMode == EditPosition) return;
14695 seekGraphUp = FALSE;
14696 MarkTargetSquares(1);
14697 if (currentMove <= backwardMostMove) {
14699 DrawPosition(full_redraw, boards[currentMove]);
14702 if (gameMode == PlayFromGameFile && !pausing)
14705 if (moveList[target][0]) {
14706 int fromX, fromY, toX, toY;
14707 toX = moveList[target][2] - AAA;
14708 toY = moveList[target][3] - ONE;
14709 if (moveList[target][1] == '@') {
14710 if (appData.highlightLastMove) {
14711 SetHighlights(-1, -1, toX, toY);
14714 fromX = moveList[target][0] - AAA;
14715 fromY = moveList[target][1] - ONE;
14716 if (target == currentMove - 1) {
14717 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14719 if (appData.highlightLastMove) {
14720 SetHighlights(fromX, fromY, toX, toY);
14724 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14725 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14726 while (currentMove > target) {
14727 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14728 // null move cannot be undone. Reload program with move history before it.
14730 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14731 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14733 SendBoard(&first, i);
14734 if(second.analyzing) SendBoard(&second, i);
14735 for(currentMove=i; currentMove<target; currentMove++) {
14736 SendMoveToProgram(currentMove, &first);
14737 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14741 SendToBoth("undo\n");
14745 currentMove = target;
14748 if (gameMode == EditGame || gameMode == EndOfGame) {
14749 whiteTimeRemaining = timeRemaining[0][currentMove];
14750 blackTimeRemaining = timeRemaining[1][currentMove];
14752 DisplayBothClocks();
14753 DisplayMove(currentMove - 1);
14754 DrawPosition(full_redraw, boards[currentMove]);
14755 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14756 // [HGM] PV info: routine tests if comment empty
14757 DisplayComment(currentMove - 1, commentList[currentMove]);
14758 ClearMap(); // [HGM] exclude: invalidate map
14764 if (gameMode == IcsExamining && !pausing) {
14765 SendToICS(ics_prefix);
14766 SendToICS("backward\n");
14768 BackwardInner(currentMove - 1);
14775 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14776 /* to optimize, we temporarily turn off analysis mode while we undo
14777 * all the moves. Otherwise we get analysis output after each undo.
14779 if (first.analysisSupport) {
14780 SendToProgram("exit\nforce\n", &first);
14781 first.analyzing = FALSE;
14785 if (gameMode == IcsExamining && !pausing) {
14786 SendToICS(ics_prefix);
14787 SendToICS("backward 999999\n");
14789 BackwardInner(backwardMostMove);
14792 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14793 /* we have fed all the moves, so reactivate analysis mode */
14794 SendToProgram("analyze\n", &first);
14795 first.analyzing = TRUE;
14796 /*first.maybeThinking = TRUE;*/
14797 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14804 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14805 if (to >= forwardMostMove) to = forwardMostMove;
14806 if (to <= backwardMostMove) to = backwardMostMove;
14807 if (to < currentMove) {
14815 RevertEvent (Boolean annotate)
14817 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14820 if (gameMode != IcsExamining) {
14821 DisplayError(_("You are not examining a game"), 0);
14825 DisplayError(_("You can't revert while pausing"), 0);
14828 SendToICS(ics_prefix);
14829 SendToICS("revert\n");
14833 RetractMoveEvent ()
14835 switch (gameMode) {
14836 case MachinePlaysWhite:
14837 case MachinePlaysBlack:
14838 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14839 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14842 if (forwardMostMove < 2) return;
14843 currentMove = forwardMostMove = forwardMostMove - 2;
14844 whiteTimeRemaining = timeRemaining[0][currentMove];
14845 blackTimeRemaining = timeRemaining[1][currentMove];
14846 DisplayBothClocks();
14847 DisplayMove(currentMove - 1);
14848 ClearHighlights();/*!! could figure this out*/
14849 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14850 SendToProgram("remove\n", &first);
14851 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14854 case BeginningOfGame:
14858 case IcsPlayingWhite:
14859 case IcsPlayingBlack:
14860 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14861 SendToICS(ics_prefix);
14862 SendToICS("takeback 2\n");
14864 SendToICS(ics_prefix);
14865 SendToICS("takeback 1\n");
14874 ChessProgramState *cps;
14876 switch (gameMode) {
14877 case MachinePlaysWhite:
14878 if (!WhiteOnMove(forwardMostMove)) {
14879 DisplayError(_("It is your turn"), 0);
14884 case MachinePlaysBlack:
14885 if (WhiteOnMove(forwardMostMove)) {
14886 DisplayError(_("It is your turn"), 0);
14891 case TwoMachinesPlay:
14892 if (WhiteOnMove(forwardMostMove) ==
14893 (first.twoMachinesColor[0] == 'w')) {
14899 case BeginningOfGame:
14903 SendToProgram("?\n", cps);
14907 TruncateGameEvent ()
14910 if (gameMode != EditGame) return;
14917 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14918 if (forwardMostMove > currentMove) {
14919 if (gameInfo.resultDetails != NULL) {
14920 free(gameInfo.resultDetails);
14921 gameInfo.resultDetails = NULL;
14922 gameInfo.result = GameUnfinished;
14924 forwardMostMove = currentMove;
14925 HistorySet(parseList, backwardMostMove, forwardMostMove,
14933 if (appData.noChessProgram) return;
14934 switch (gameMode) {
14935 case MachinePlaysWhite:
14936 if (WhiteOnMove(forwardMostMove)) {
14937 DisplayError(_("Wait until your turn"), 0);
14941 case BeginningOfGame:
14942 case MachinePlaysBlack:
14943 if (!WhiteOnMove(forwardMostMove)) {
14944 DisplayError(_("Wait until your turn"), 0);
14949 DisplayError(_("No hint available"), 0);
14952 SendToProgram("hint\n", &first);
14953 hintRequested = TRUE;
14959 if (appData.noChessProgram) return;
14960 switch (gameMode) {
14961 case MachinePlaysWhite:
14962 if (WhiteOnMove(forwardMostMove)) {
14963 DisplayError(_("Wait until your turn"), 0);
14967 case BeginningOfGame:
14968 case MachinePlaysBlack:
14969 if (!WhiteOnMove(forwardMostMove)) {
14970 DisplayError(_("Wait until your turn"), 0);
14975 EditPositionDone(TRUE);
14977 case TwoMachinesPlay:
14982 SendToProgram("bk\n", &first);
14983 bookOutput[0] = NULLCHAR;
14984 bookRequested = TRUE;
14990 char *tags = PGNTags(&gameInfo);
14991 TagsPopUp(tags, CmailMsg());
14995 /* end button procedures */
14998 PrintPosition (FILE *fp, int move)
15002 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15003 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15004 char c = PieceToChar(boards[move][i][j]);
15005 fputc(c == 'x' ? '.' : c, fp);
15006 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15009 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15010 fprintf(fp, "white to play\n");
15012 fprintf(fp, "black to play\n");
15016 PrintOpponents (FILE *fp)
15018 if (gameInfo.white != NULL) {
15019 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15025 /* Find last component of program's own name, using some heuristics */
15027 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15030 int local = (strcmp(host, "localhost") == 0);
15031 while (!local && (p = strchr(prog, ';')) != NULL) {
15033 while (*p == ' ') p++;
15036 if (*prog == '"' || *prog == '\'') {
15037 q = strchr(prog + 1, *prog);
15039 q = strchr(prog, ' ');
15041 if (q == NULL) q = prog + strlen(prog);
15043 while (p >= prog && *p != '/' && *p != '\\') p--;
15045 if(p == prog && *p == '"') p++;
15047 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15048 memcpy(buf, p, q - p);
15049 buf[q - p] = NULLCHAR;
15057 TimeControlTagValue ()
15060 if (!appData.clockMode) {
15061 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15062 } else if (movesPerSession > 0) {
15063 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15064 } else if (timeIncrement == 0) {
15065 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15067 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15069 return StrSave(buf);
15075 /* This routine is used only for certain modes */
15076 VariantClass v = gameInfo.variant;
15077 ChessMove r = GameUnfinished;
15080 if(keepInfo) return;
15082 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15083 r = gameInfo.result;
15084 p = gameInfo.resultDetails;
15085 gameInfo.resultDetails = NULL;
15087 ClearGameInfo(&gameInfo);
15088 gameInfo.variant = v;
15090 switch (gameMode) {
15091 case MachinePlaysWhite:
15092 gameInfo.event = StrSave( appData.pgnEventHeader );
15093 gameInfo.site = StrSave(HostName());
15094 gameInfo.date = PGNDate();
15095 gameInfo.round = StrSave("-");
15096 gameInfo.white = StrSave(first.tidy);
15097 gameInfo.black = StrSave(UserName());
15098 gameInfo.timeControl = TimeControlTagValue();
15101 case MachinePlaysBlack:
15102 gameInfo.event = StrSave( appData.pgnEventHeader );
15103 gameInfo.site = StrSave(HostName());
15104 gameInfo.date = PGNDate();
15105 gameInfo.round = StrSave("-");
15106 gameInfo.white = StrSave(UserName());
15107 gameInfo.black = StrSave(first.tidy);
15108 gameInfo.timeControl = TimeControlTagValue();
15111 case TwoMachinesPlay:
15112 gameInfo.event = StrSave( appData.pgnEventHeader );
15113 gameInfo.site = StrSave(HostName());
15114 gameInfo.date = PGNDate();
15117 snprintf(buf, MSG_SIZ, "%d", roundNr);
15118 gameInfo.round = StrSave(buf);
15120 gameInfo.round = StrSave("-");
15122 if (first.twoMachinesColor[0] == 'w') {
15123 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15124 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15126 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15127 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15129 gameInfo.timeControl = TimeControlTagValue();
15133 gameInfo.event = StrSave("Edited game");
15134 gameInfo.site = StrSave(HostName());
15135 gameInfo.date = PGNDate();
15136 gameInfo.round = StrSave("-");
15137 gameInfo.white = StrSave("-");
15138 gameInfo.black = StrSave("-");
15139 gameInfo.result = r;
15140 gameInfo.resultDetails = p;
15144 gameInfo.event = StrSave("Edited position");
15145 gameInfo.site = StrSave(HostName());
15146 gameInfo.date = PGNDate();
15147 gameInfo.round = StrSave("-");
15148 gameInfo.white = StrSave("-");
15149 gameInfo.black = StrSave("-");
15152 case IcsPlayingWhite:
15153 case IcsPlayingBlack:
15158 case PlayFromGameFile:
15159 gameInfo.event = StrSave("Game from non-PGN file");
15160 gameInfo.site = StrSave(HostName());
15161 gameInfo.date = PGNDate();
15162 gameInfo.round = StrSave("-");
15163 gameInfo.white = StrSave("?");
15164 gameInfo.black = StrSave("?");
15173 ReplaceComment (int index, char *text)
15179 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15180 pvInfoList[index-1].depth == len &&
15181 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15182 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15183 while (*text == '\n') text++;
15184 len = strlen(text);
15185 while (len > 0 && text[len - 1] == '\n') len--;
15187 if (commentList[index] != NULL)
15188 free(commentList[index]);
15191 commentList[index] = NULL;
15194 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15195 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15196 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15197 commentList[index] = (char *) malloc(len + 2);
15198 strncpy(commentList[index], text, len);
15199 commentList[index][len] = '\n';
15200 commentList[index][len + 1] = NULLCHAR;
15202 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15204 commentList[index] = (char *) malloc(len + 7);
15205 safeStrCpy(commentList[index], "{\n", 3);
15206 safeStrCpy(commentList[index]+2, text, len+1);
15207 commentList[index][len+2] = NULLCHAR;
15208 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15209 strcat(commentList[index], "\n}\n");
15214 CrushCRs (char *text)
15222 if (ch == '\r') continue;
15224 } while (ch != '\0');
15228 AppendComment (int index, char *text, Boolean addBraces)
15229 /* addBraces tells if we should add {} */
15234 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15235 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15238 while (*text == '\n') text++;
15239 len = strlen(text);
15240 while (len > 0 && text[len - 1] == '\n') len--;
15241 text[len] = NULLCHAR;
15243 if (len == 0) return;
15245 if (commentList[index] != NULL) {
15246 Boolean addClosingBrace = addBraces;
15247 old = commentList[index];
15248 oldlen = strlen(old);
15249 while(commentList[index][oldlen-1] == '\n')
15250 commentList[index][--oldlen] = NULLCHAR;
15251 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15252 safeStrCpy(commentList[index], old, oldlen + len + 6);
15254 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15255 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15256 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15257 while (*text == '\n') { text++; len--; }
15258 commentList[index][--oldlen] = NULLCHAR;
15260 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15261 else strcat(commentList[index], "\n");
15262 strcat(commentList[index], text);
15263 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15264 else strcat(commentList[index], "\n");
15266 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15268 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15269 else commentList[index][0] = NULLCHAR;
15270 strcat(commentList[index], text);
15271 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15272 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15277 FindStr (char * text, char * sub_text)
15279 char * result = strstr( text, sub_text );
15281 if( result != NULL ) {
15282 result += strlen( sub_text );
15288 /* [AS] Try to extract PV info from PGN comment */
15289 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15291 GetInfoFromComment (int index, char * text)
15293 char * sep = text, *p;
15295 if( text != NULL && index > 0 ) {
15298 int time = -1, sec = 0, deci;
15299 char * s_eval = FindStr( text, "[%eval " );
15300 char * s_emt = FindStr( text, "[%emt " );
15302 if( s_eval != NULL || s_emt != NULL ) {
15306 if( s_eval != NULL ) {
15307 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15311 if( delim != ']' ) {
15316 if( s_emt != NULL ) {
15321 /* We expect something like: [+|-]nnn.nn/dd */
15324 if(*text != '{') return text; // [HGM] braces: must be normal comment
15326 sep = strchr( text, '/' );
15327 if( sep == NULL || sep < (text+4) ) {
15332 if(p[1] == '(') { // comment starts with PV
15333 p = strchr(p, ')'); // locate end of PV
15334 if(p == NULL || sep < p+5) return text;
15335 // at this point we have something like "{(.*) +0.23/6 ..."
15336 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15337 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15338 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15340 time = -1; sec = -1; deci = -1;
15341 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15342 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15343 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15344 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15348 if( score_lo < 0 || score_lo >= 100 ) {
15352 if(sec >= 0) time = 600*time + 10*sec; else
15353 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15355 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15357 /* [HGM] PV time: now locate end of PV info */
15358 while( *++sep >= '0' && *sep <= '9'); // strip depth
15360 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15362 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15364 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15365 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15376 pvInfoList[index-1].depth = depth;
15377 pvInfoList[index-1].score = score;
15378 pvInfoList[index-1].time = 10*time; // centi-sec
15379 if(*sep == '}') *sep = 0; else *--sep = '{';
15380 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15386 SendToProgram (char *message, ChessProgramState *cps)
15388 int count, outCount, error;
15391 if (cps->pr == NoProc) return;
15394 if (appData.debugMode) {
15397 fprintf(debugFP, "%ld >%-6s: %s",
15398 SubtractTimeMarks(&now, &programStartTime),
15399 cps->which, message);
15401 fprintf(serverFP, "%ld >%-6s: %s",
15402 SubtractTimeMarks(&now, &programStartTime),
15403 cps->which, message), fflush(serverFP);
15406 count = strlen(message);
15407 outCount = OutputToProcess(cps->pr, message, count, &error);
15408 if (outCount < count && !exiting
15409 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15410 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15411 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15412 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15413 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15414 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15415 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15416 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15418 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15419 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15420 gameInfo.result = res;
15422 gameInfo.resultDetails = StrSave(buf);
15424 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15425 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15430 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15434 ChessProgramState *cps = (ChessProgramState *)closure;
15436 if (isr != cps->isr) return; /* Killed intentionally */
15439 RemoveInputSource(cps->isr);
15440 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15441 _(cps->which), cps->program);
15442 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15443 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15444 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15445 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15446 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15447 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15449 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15450 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15451 gameInfo.result = res;
15453 gameInfo.resultDetails = StrSave(buf);
15455 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15456 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15458 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15459 _(cps->which), cps->program);
15460 RemoveInputSource(cps->isr);
15462 /* [AS] Program is misbehaving badly... kill it */
15463 if( count == -2 ) {
15464 DestroyChildProcess( cps->pr, 9 );
15468 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15473 if ((end_str = strchr(message, '\r')) != NULL)
15474 *end_str = NULLCHAR;
15475 if ((end_str = strchr(message, '\n')) != NULL)
15476 *end_str = NULLCHAR;
15478 if (appData.debugMode) {
15479 TimeMark now; int print = 1;
15480 char *quote = ""; char c; int i;
15482 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15483 char start = message[0];
15484 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15485 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15486 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15487 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15488 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15489 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15490 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15491 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15492 sscanf(message, "hint: %c", &c)!=1 &&
15493 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15494 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15495 print = (appData.engineComments >= 2);
15497 message[0] = start; // restore original message
15501 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15502 SubtractTimeMarks(&now, &programStartTime), cps->which,
15506 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15507 SubtractTimeMarks(&now, &programStartTime), cps->which,
15509 message), fflush(serverFP);
15513 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15514 if (appData.icsEngineAnalyze) {
15515 if (strstr(message, "whisper") != NULL ||
15516 strstr(message, "kibitz") != NULL ||
15517 strstr(message, "tellics") != NULL) return;
15520 HandleMachineMove(message, cps);
15525 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15530 if( timeControl_2 > 0 ) {
15531 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15532 tc = timeControl_2;
15535 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15536 inc /= cps->timeOdds;
15537 st /= cps->timeOdds;
15539 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15542 /* Set exact time per move, normally using st command */
15543 if (cps->stKludge) {
15544 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15546 if (seconds == 0) {
15547 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15549 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15552 snprintf(buf, MSG_SIZ, "st %d\n", st);
15555 /* Set conventional or incremental time control, using level command */
15556 if (seconds == 0) {
15557 /* Note old gnuchess bug -- minutes:seconds used to not work.
15558 Fixed in later versions, but still avoid :seconds
15559 when seconds is 0. */
15560 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15562 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15563 seconds, inc/1000.);
15566 SendToProgram(buf, cps);
15568 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15569 /* Orthogonally, limit search to given depth */
15571 if (cps->sdKludge) {
15572 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15574 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15576 SendToProgram(buf, cps);
15579 if(cps->nps >= 0) { /* [HGM] nps */
15580 if(cps->supportsNPS == FALSE)
15581 cps->nps = -1; // don't use if engine explicitly says not supported!
15583 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15584 SendToProgram(buf, cps);
15589 ChessProgramState *
15591 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15593 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15594 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15600 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15602 char message[MSG_SIZ];
15605 /* Note: this routine must be called when the clocks are stopped
15606 or when they have *just* been set or switched; otherwise
15607 it will be off by the time since the current tick started.
15609 if (machineWhite) {
15610 time = whiteTimeRemaining / 10;
15611 otime = blackTimeRemaining / 10;
15613 time = blackTimeRemaining / 10;
15614 otime = whiteTimeRemaining / 10;
15616 /* [HGM] translate opponent's time by time-odds factor */
15617 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15619 if (time <= 0) time = 1;
15620 if (otime <= 0) otime = 1;
15622 snprintf(message, MSG_SIZ, "time %ld\n", time);
15623 SendToProgram(message, cps);
15625 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15626 SendToProgram(message, cps);
15630 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15633 int len = strlen(name);
15636 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15638 sscanf(*p, "%d", &val);
15640 while (**p && **p != ' ')
15642 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15643 SendToProgram(buf, cps);
15650 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15653 int len = strlen(name);
15654 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15656 sscanf(*p, "%d", loc);
15657 while (**p && **p != ' ') (*p)++;
15658 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15659 SendToProgram(buf, cps);
15666 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15669 int len = strlen(name);
15670 if (strncmp((*p), name, len) == 0
15671 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15673 sscanf(*p, "%[^\"]", loc);
15674 while (**p && **p != '\"') (*p)++;
15675 if (**p == '\"') (*p)++;
15676 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15677 SendToProgram(buf, cps);
15684 ParseOption (Option *opt, ChessProgramState *cps)
15685 // [HGM] options: process the string that defines an engine option, and determine
15686 // name, type, default value, and allowed value range
15688 char *p, *q, buf[MSG_SIZ];
15689 int n, min = (-1)<<31, max = 1<<31, def;
15691 if(p = strstr(opt->name, " -spin ")) {
15692 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15693 if(max < min) max = min; // enforce consistency
15694 if(def < min) def = min;
15695 if(def > max) def = max;
15700 } else if((p = strstr(opt->name, " -slider "))) {
15701 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15702 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15703 if(max < min) max = min; // enforce consistency
15704 if(def < min) def = min;
15705 if(def > max) def = max;
15709 opt->type = Spin; // Slider;
15710 } else if((p = strstr(opt->name, " -string "))) {
15711 opt->textValue = p+9;
15712 opt->type = TextBox;
15713 } else if((p = strstr(opt->name, " -file "))) {
15714 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15715 opt->textValue = p+7;
15716 opt->type = FileName; // FileName;
15717 } else if((p = strstr(opt->name, " -path "))) {
15718 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15719 opt->textValue = p+7;
15720 opt->type = PathName; // PathName;
15721 } else if(p = strstr(opt->name, " -check ")) {
15722 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15723 opt->value = (def != 0);
15724 opt->type = CheckBox;
15725 } else if(p = strstr(opt->name, " -combo ")) {
15726 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15727 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15728 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15729 opt->value = n = 0;
15730 while(q = StrStr(q, " /// ")) {
15731 n++; *q = 0; // count choices, and null-terminate each of them
15733 if(*q == '*') { // remember default, which is marked with * prefix
15737 cps->comboList[cps->comboCnt++] = q;
15739 cps->comboList[cps->comboCnt++] = NULL;
15741 opt->type = ComboBox;
15742 } else if(p = strstr(opt->name, " -button")) {
15743 opt->type = Button;
15744 } else if(p = strstr(opt->name, " -save")) {
15745 opt->type = SaveButton;
15746 } else return FALSE;
15747 *p = 0; // terminate option name
15748 // now look if the command-line options define a setting for this engine option.
15749 if(cps->optionSettings && cps->optionSettings[0])
15750 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15751 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15752 snprintf(buf, MSG_SIZ, "option %s", p);
15753 if(p = strstr(buf, ",")) *p = 0;
15754 if(q = strchr(buf, '=')) switch(opt->type) {
15756 for(n=0; n<opt->max; n++)
15757 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15760 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15764 opt->value = atoi(q+1);
15769 SendToProgram(buf, cps);
15775 FeatureDone (ChessProgramState *cps, int val)
15777 DelayedEventCallback cb = GetDelayedEvent();
15778 if ((cb == InitBackEnd3 && cps == &first) ||
15779 (cb == SettingsMenuIfReady && cps == &second) ||
15780 (cb == LoadEngine) ||
15781 (cb == TwoMachinesEventIfReady)) {
15782 CancelDelayedEvent();
15783 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15785 cps->initDone = val;
15788 /* Parse feature command from engine */
15790 ParseFeatures (char *args, ChessProgramState *cps)
15798 while (*p == ' ') p++;
15799 if (*p == NULLCHAR) return;
15801 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15802 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15803 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15804 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15805 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15806 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15807 if (BoolFeature(&p, "reuse", &val, cps)) {
15808 /* Engine can disable reuse, but can't enable it if user said no */
15809 if (!val) cps->reuse = FALSE;
15812 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15813 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15814 if (gameMode == TwoMachinesPlay) {
15815 DisplayTwoMachinesTitle();
15821 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15822 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15823 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15824 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15825 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15826 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15827 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15828 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15829 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15830 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15831 if (IntFeature(&p, "done", &val, cps)) {
15832 FeatureDone(cps, val);
15835 /* Added by Tord: */
15836 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15837 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15838 /* End of additions by Tord */
15840 /* [HGM] added features: */
15841 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15842 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15843 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15844 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15845 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15846 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15847 if (StringFeature(&p, "option", buf, cps)) {
15848 FREE(cps->option[cps->nrOptions].name);
15849 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15850 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15851 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15852 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15853 SendToProgram(buf, cps);
15856 if(cps->nrOptions >= MAX_OPTIONS) {
15858 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15859 DisplayError(buf, 0);
15863 /* End of additions by HGM */
15865 /* unknown feature: complain and skip */
15867 while (*q && *q != '=') q++;
15868 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15869 SendToProgram(buf, cps);
15875 while (*p && *p != '\"') p++;
15876 if (*p == '\"') p++;
15878 while (*p && *p != ' ') p++;
15886 PeriodicUpdatesEvent (int newState)
15888 if (newState == appData.periodicUpdates)
15891 appData.periodicUpdates=newState;
15893 /* Display type changes, so update it now */
15894 // DisplayAnalysis();
15896 /* Get the ball rolling again... */
15898 AnalysisPeriodicEvent(1);
15899 StartAnalysisClock();
15904 PonderNextMoveEvent (int newState)
15906 if (newState == appData.ponderNextMove) return;
15907 if (gameMode == EditPosition) EditPositionDone(TRUE);
15909 SendToProgram("hard\n", &first);
15910 if (gameMode == TwoMachinesPlay) {
15911 SendToProgram("hard\n", &second);
15914 SendToProgram("easy\n", &first);
15915 thinkOutput[0] = NULLCHAR;
15916 if (gameMode == TwoMachinesPlay) {
15917 SendToProgram("easy\n", &second);
15920 appData.ponderNextMove = newState;
15924 NewSettingEvent (int option, int *feature, char *command, int value)
15928 if (gameMode == EditPosition) EditPositionDone(TRUE);
15929 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15930 if(feature == NULL || *feature) SendToProgram(buf, &first);
15931 if (gameMode == TwoMachinesPlay) {
15932 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15937 ShowThinkingEvent ()
15938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15940 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15941 int newState = appData.showThinking
15942 // [HGM] thinking: other features now need thinking output as well
15943 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15945 if (oldState == newState) return;
15946 oldState = newState;
15947 if (gameMode == EditPosition) EditPositionDone(TRUE);
15949 SendToProgram("post\n", &first);
15950 if (gameMode == TwoMachinesPlay) {
15951 SendToProgram("post\n", &second);
15954 SendToProgram("nopost\n", &first);
15955 thinkOutput[0] = NULLCHAR;
15956 if (gameMode == TwoMachinesPlay) {
15957 SendToProgram("nopost\n", &second);
15960 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15964 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15966 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15967 if (pr == NoProc) return;
15968 AskQuestion(title, question, replyPrefix, pr);
15972 TypeInEvent (char firstChar)
15974 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15975 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15976 gameMode == AnalyzeMode || gameMode == EditGame ||
15977 gameMode == EditPosition || gameMode == IcsExamining ||
15978 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15979 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15980 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15981 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15982 gameMode == Training) PopUpMoveDialog(firstChar);
15986 TypeInDoneEvent (char *move)
15989 int n, fromX, fromY, toX, toY;
15991 ChessMove moveType;
15994 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15995 EditPositionPasteFEN(move);
15998 // [HGM] movenum: allow move number to be typed in any mode
15999 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16003 // undocumented kludge: allow command-line option to be typed in!
16004 // (potentially fatal, and does not implement the effect of the option.)
16005 // should only be used for options that are values on which future decisions will be made,
16006 // and definitely not on options that would be used during initialization.
16007 if(strstr(move, "!!! -") == move) {
16008 ParseArgsFromString(move+4);
16012 if (gameMode != EditGame && currentMove != forwardMostMove &&
16013 gameMode != Training) {
16014 DisplayMoveError(_("Displayed move is not current"));
16016 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16017 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16018 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16019 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16020 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16021 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16023 DisplayMoveError(_("Could not parse move"));
16029 DisplayMove (int moveNumber)
16031 char message[MSG_SIZ];
16033 char cpThinkOutput[MSG_SIZ];
16035 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16037 if (moveNumber == forwardMostMove - 1 ||
16038 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16040 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16042 if (strchr(cpThinkOutput, '\n')) {
16043 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16046 *cpThinkOutput = NULLCHAR;
16049 /* [AS] Hide thinking from human user */
16050 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16051 *cpThinkOutput = NULLCHAR;
16052 if( thinkOutput[0] != NULLCHAR ) {
16055 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16056 cpThinkOutput[i] = '.';
16058 cpThinkOutput[i] = NULLCHAR;
16059 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16063 if (moveNumber == forwardMostMove - 1 &&
16064 gameInfo.resultDetails != NULL) {
16065 if (gameInfo.resultDetails[0] == NULLCHAR) {
16066 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16068 snprintf(res, MSG_SIZ, " {%s} %s",
16069 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16075 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16076 DisplayMessage(res, cpThinkOutput);
16078 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16079 WhiteOnMove(moveNumber) ? " " : ".. ",
16080 parseList[moveNumber], res);
16081 DisplayMessage(message, cpThinkOutput);
16086 DisplayComment (int moveNumber, char *text)
16088 char title[MSG_SIZ];
16090 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16091 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16093 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16094 WhiteOnMove(moveNumber) ? " " : ".. ",
16095 parseList[moveNumber]);
16097 if (text != NULL && (appData.autoDisplayComment || commentUp))
16098 CommentPopUp(title, text);
16101 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16102 * might be busy thinking or pondering. It can be omitted if your
16103 * gnuchess is configured to stop thinking immediately on any user
16104 * input. However, that gnuchess feature depends on the FIONREAD
16105 * ioctl, which does not work properly on some flavors of Unix.
16108 Attention (ChessProgramState *cps)
16111 if (!cps->useSigint) return;
16112 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16113 switch (gameMode) {
16114 case MachinePlaysWhite:
16115 case MachinePlaysBlack:
16116 case TwoMachinesPlay:
16117 case IcsPlayingWhite:
16118 case IcsPlayingBlack:
16121 /* Skip if we know it isn't thinking */
16122 if (!cps->maybeThinking) return;
16123 if (appData.debugMode)
16124 fprintf(debugFP, "Interrupting %s\n", cps->which);
16125 InterruptChildProcess(cps->pr);
16126 cps->maybeThinking = FALSE;
16131 #endif /*ATTENTION*/
16137 if (whiteTimeRemaining <= 0) {
16140 if (appData.icsActive) {
16141 if (appData.autoCallFlag &&
16142 gameMode == IcsPlayingBlack && !blackFlag) {
16143 SendToICS(ics_prefix);
16144 SendToICS("flag\n");
16148 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16150 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16151 if (appData.autoCallFlag) {
16152 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16159 if (blackTimeRemaining <= 0) {
16162 if (appData.icsActive) {
16163 if (appData.autoCallFlag &&
16164 gameMode == IcsPlayingWhite && !whiteFlag) {
16165 SendToICS(ics_prefix);
16166 SendToICS("flag\n");
16170 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16172 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16173 if (appData.autoCallFlag) {
16174 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16185 CheckTimeControl ()
16187 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16188 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16191 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16193 if ( !WhiteOnMove(forwardMostMove) ) {
16194 /* White made time control */
16195 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16196 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16197 /* [HGM] time odds: correct new time quota for time odds! */
16198 / WhitePlayer()->timeOdds;
16199 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16201 lastBlack -= blackTimeRemaining;
16202 /* Black made time control */
16203 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16204 / WhitePlayer()->other->timeOdds;
16205 lastWhite = whiteTimeRemaining;
16210 DisplayBothClocks ()
16212 int wom = gameMode == EditPosition ?
16213 !blackPlaysFirst : WhiteOnMove(currentMove);
16214 DisplayWhiteClock(whiteTimeRemaining, wom);
16215 DisplayBlackClock(blackTimeRemaining, !wom);
16219 /* Timekeeping seems to be a portability nightmare. I think everyone
16220 has ftime(), but I'm really not sure, so I'm including some ifdefs
16221 to use other calls if you don't. Clocks will be less accurate if
16222 you have neither ftime nor gettimeofday.
16225 /* VS 2008 requires the #include outside of the function */
16226 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16227 #include <sys/timeb.h>
16230 /* Get the current time as a TimeMark */
16232 GetTimeMark (TimeMark *tm)
16234 #if HAVE_GETTIMEOFDAY
16236 struct timeval timeVal;
16237 struct timezone timeZone;
16239 gettimeofday(&timeVal, &timeZone);
16240 tm->sec = (long) timeVal.tv_sec;
16241 tm->ms = (int) (timeVal.tv_usec / 1000L);
16243 #else /*!HAVE_GETTIMEOFDAY*/
16246 // include <sys/timeb.h> / moved to just above start of function
16247 struct timeb timeB;
16250 tm->sec = (long) timeB.time;
16251 tm->ms = (int) timeB.millitm;
16253 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16254 tm->sec = (long) time(NULL);
16260 /* Return the difference in milliseconds between two
16261 time marks. We assume the difference will fit in a long!
16264 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16266 return 1000L*(tm2->sec - tm1->sec) +
16267 (long) (tm2->ms - tm1->ms);
16272 * Code to manage the game clocks.
16274 * In tournament play, black starts the clock and then white makes a move.
16275 * We give the human user a slight advantage if he is playing white---the
16276 * clocks don't run until he makes his first move, so it takes zero time.
16277 * Also, we don't account for network lag, so we could get out of sync
16278 * with GNU Chess's clock -- but then, referees are always right.
16281 static TimeMark tickStartTM;
16282 static long intendedTickLength;
16285 NextTickLength (long timeRemaining)
16287 long nominalTickLength, nextTickLength;
16289 if (timeRemaining > 0L && timeRemaining <= 10000L)
16290 nominalTickLength = 100L;
16292 nominalTickLength = 1000L;
16293 nextTickLength = timeRemaining % nominalTickLength;
16294 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16296 return nextTickLength;
16299 /* Adjust clock one minute up or down */
16301 AdjustClock (Boolean which, int dir)
16303 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16304 if(which) blackTimeRemaining += 60000*dir;
16305 else whiteTimeRemaining += 60000*dir;
16306 DisplayBothClocks();
16307 adjustedClock = TRUE;
16310 /* Stop clocks and reset to a fresh time control */
16314 (void) StopClockTimer();
16315 if (appData.icsActive) {
16316 whiteTimeRemaining = blackTimeRemaining = 0;
16317 } else if (searchTime) {
16318 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16319 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16320 } else { /* [HGM] correct new time quote for time odds */
16321 whiteTC = blackTC = fullTimeControlString;
16322 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16323 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16325 if (whiteFlag || blackFlag) {
16327 whiteFlag = blackFlag = FALSE;
16329 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16330 DisplayBothClocks();
16331 adjustedClock = FALSE;
16334 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16336 /* Decrement running clock by amount of time that has passed */
16340 long timeRemaining;
16341 long lastTickLength, fudge;
16344 if (!appData.clockMode) return;
16345 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16349 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16351 /* Fudge if we woke up a little too soon */
16352 fudge = intendedTickLength - lastTickLength;
16353 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16355 if (WhiteOnMove(forwardMostMove)) {
16356 if(whiteNPS >= 0) lastTickLength = 0;
16357 timeRemaining = whiteTimeRemaining -= lastTickLength;
16358 if(timeRemaining < 0 && !appData.icsActive) {
16359 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16360 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16361 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16362 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16365 DisplayWhiteClock(whiteTimeRemaining - fudge,
16366 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16368 if(blackNPS >= 0) lastTickLength = 0;
16369 timeRemaining = blackTimeRemaining -= lastTickLength;
16370 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16371 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16373 blackStartMove = forwardMostMove;
16374 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16377 DisplayBlackClock(blackTimeRemaining - fudge,
16378 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16380 if (CheckFlags()) return;
16382 if(twoBoards) { // count down secondary board's clocks as well
16383 activePartnerTime -= lastTickLength;
16385 if(activePartner == 'W')
16386 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16388 DisplayBlackClock(activePartnerTime, TRUE);
16393 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16394 StartClockTimer(intendedTickLength);
16396 /* if the time remaining has fallen below the alarm threshold, sound the
16397 * alarm. if the alarm has sounded and (due to a takeback or time control
16398 * with increment) the time remaining has increased to a level above the
16399 * threshold, reset the alarm so it can sound again.
16402 if (appData.icsActive && appData.icsAlarm) {
16404 /* make sure we are dealing with the user's clock */
16405 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16406 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16409 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16410 alarmSounded = FALSE;
16411 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16413 alarmSounded = TRUE;
16419 /* A player has just moved, so stop the previously running
16420 clock and (if in clock mode) start the other one.
16421 We redisplay both clocks in case we're in ICS mode, because
16422 ICS gives us an update to both clocks after every move.
16423 Note that this routine is called *after* forwardMostMove
16424 is updated, so the last fractional tick must be subtracted
16425 from the color that is *not* on move now.
16428 SwitchClocks (int newMoveNr)
16430 long lastTickLength;
16432 int flagged = FALSE;
16436 if (StopClockTimer() && appData.clockMode) {
16437 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16438 if (!WhiteOnMove(forwardMostMove)) {
16439 if(blackNPS >= 0) lastTickLength = 0;
16440 blackTimeRemaining -= lastTickLength;
16441 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16442 // if(pvInfoList[forwardMostMove].time == -1)
16443 pvInfoList[forwardMostMove].time = // use GUI time
16444 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16446 if(whiteNPS >= 0) lastTickLength = 0;
16447 whiteTimeRemaining -= lastTickLength;
16448 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16449 // if(pvInfoList[forwardMostMove].time == -1)
16450 pvInfoList[forwardMostMove].time =
16451 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16453 flagged = CheckFlags();
16455 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16456 CheckTimeControl();
16458 if (flagged || !appData.clockMode) return;
16460 switch (gameMode) {
16461 case MachinePlaysBlack:
16462 case MachinePlaysWhite:
16463 case BeginningOfGame:
16464 if (pausing) return;
16468 case PlayFromGameFile:
16476 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16477 if(WhiteOnMove(forwardMostMove))
16478 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16479 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16483 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16484 whiteTimeRemaining : blackTimeRemaining);
16485 StartClockTimer(intendedTickLength);
16489 /* Stop both clocks */
16493 long lastTickLength;
16496 if (!StopClockTimer()) return;
16497 if (!appData.clockMode) return;
16501 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16502 if (WhiteOnMove(forwardMostMove)) {
16503 if(whiteNPS >= 0) lastTickLength = 0;
16504 whiteTimeRemaining -= lastTickLength;
16505 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16507 if(blackNPS >= 0) lastTickLength = 0;
16508 blackTimeRemaining -= lastTickLength;
16509 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16514 /* Start clock of player on move. Time may have been reset, so
16515 if clock is already running, stop and restart it. */
16519 (void) StopClockTimer(); /* in case it was running already */
16520 DisplayBothClocks();
16521 if (CheckFlags()) return;
16523 if (!appData.clockMode) return;
16524 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16526 GetTimeMark(&tickStartTM);
16527 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16528 whiteTimeRemaining : blackTimeRemaining);
16530 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16531 whiteNPS = blackNPS = -1;
16532 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16533 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16534 whiteNPS = first.nps;
16535 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16536 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16537 blackNPS = first.nps;
16538 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16539 whiteNPS = second.nps;
16540 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16541 blackNPS = second.nps;
16542 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16544 StartClockTimer(intendedTickLength);
16548 TimeString (long ms)
16550 long second, minute, hour, day;
16552 static char buf[32];
16554 if (ms > 0 && ms <= 9900) {
16555 /* convert milliseconds to tenths, rounding up */
16556 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16558 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16562 /* convert milliseconds to seconds, rounding up */
16563 /* use floating point to avoid strangeness of integer division
16564 with negative dividends on many machines */
16565 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16572 day = second / (60 * 60 * 24);
16573 second = second % (60 * 60 * 24);
16574 hour = second / (60 * 60);
16575 second = second % (60 * 60);
16576 minute = second / 60;
16577 second = second % 60;
16580 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16581 sign, day, hour, minute, second);
16583 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16585 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16592 * This is necessary because some C libraries aren't ANSI C compliant yet.
16595 StrStr (char *string, char *match)
16599 length = strlen(match);
16601 for (i = strlen(string) - length; i >= 0; i--, string++)
16602 if (!strncmp(match, string, length))
16609 StrCaseStr (char *string, char *match)
16613 length = strlen(match);
16615 for (i = strlen(string) - length; i >= 0; i--, string++) {
16616 for (j = 0; j < length; j++) {
16617 if (ToLower(match[j]) != ToLower(string[j]))
16620 if (j == length) return string;
16628 StrCaseCmp (char *s1, char *s2)
16633 c1 = ToLower(*s1++);
16634 c2 = ToLower(*s2++);
16635 if (c1 > c2) return 1;
16636 if (c1 < c2) return -1;
16637 if (c1 == NULLCHAR) return 0;
16645 return isupper(c) ? tolower(c) : c;
16652 return islower(c) ? toupper(c) : c;
16654 #endif /* !_amigados */
16661 if ((ret = (char *) malloc(strlen(s) + 1)))
16663 safeStrCpy(ret, s, strlen(s)+1);
16669 StrSavePtr (char *s, char **savePtr)
16674 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16675 safeStrCpy(*savePtr, s, strlen(s)+1);
16687 clock = time((time_t *)NULL);
16688 tm = localtime(&clock);
16689 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16690 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16691 return StrSave(buf);
16696 PositionToFEN (int move, char *overrideCastling)
16698 int i, j, fromX, fromY, toX, toY;
16705 whiteToPlay = (gameMode == EditPosition) ?
16706 !blackPlaysFirst : (move % 2 == 0);
16709 /* Piece placement data */
16710 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16711 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16713 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16714 if (boards[move][i][j] == EmptySquare) {
16716 } else { ChessSquare piece = boards[move][i][j];
16717 if (emptycount > 0) {
16718 if(emptycount<10) /* [HGM] can be >= 10 */
16719 *p++ = '0' + emptycount;
16720 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16723 if(PieceToChar(piece) == '+') {
16724 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16726 piece = (ChessSquare)(DEMOTED piece);
16728 *p++ = PieceToChar(piece);
16730 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16731 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16736 if (emptycount > 0) {
16737 if(emptycount<10) /* [HGM] can be >= 10 */
16738 *p++ = '0' + emptycount;
16739 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16746 /* [HGM] print Crazyhouse or Shogi holdings */
16747 if( gameInfo.holdingsWidth ) {
16748 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16750 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16751 piece = boards[move][i][BOARD_WIDTH-1];
16752 if( piece != EmptySquare )
16753 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16754 *p++ = PieceToChar(piece);
16756 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16757 piece = boards[move][BOARD_HEIGHT-i-1][0];
16758 if( piece != EmptySquare )
16759 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16760 *p++ = PieceToChar(piece);
16763 if( q == p ) *p++ = '-';
16769 *p++ = whiteToPlay ? 'w' : 'b';
16772 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16773 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16775 if(nrCastlingRights) {
16777 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16778 /* [HGM] write directly from rights */
16779 if(boards[move][CASTLING][2] != NoRights &&
16780 boards[move][CASTLING][0] != NoRights )
16781 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16782 if(boards[move][CASTLING][2] != NoRights &&
16783 boards[move][CASTLING][1] != NoRights )
16784 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16785 if(boards[move][CASTLING][5] != NoRights &&
16786 boards[move][CASTLING][3] != NoRights )
16787 *p++ = boards[move][CASTLING][3] + AAA;
16788 if(boards[move][CASTLING][5] != NoRights &&
16789 boards[move][CASTLING][4] != NoRights )
16790 *p++ = boards[move][CASTLING][4] + AAA;
16793 /* [HGM] write true castling rights */
16794 if( nrCastlingRights == 6 ) {
16796 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16797 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
16798 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16799 boards[move][CASTLING][2] != NoRights );
16800 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16801 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16802 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16803 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16804 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16808 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16809 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
16810 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16811 boards[move][CASTLING][5] != NoRights );
16812 if(gameInfo.variant == VariantSChess) {
16813 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16814 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16815 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16816 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16821 if (q == p) *p++ = '-'; /* No castling rights */
16825 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16826 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16827 /* En passant target square */
16828 if (move > backwardMostMove) {
16829 fromX = moveList[move - 1][0] - AAA;
16830 fromY = moveList[move - 1][1] - ONE;
16831 toX = moveList[move - 1][2] - AAA;
16832 toY = moveList[move - 1][3] - ONE;
16833 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16834 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16835 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16837 /* 2-square pawn move just happened */
16839 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16843 } else if(move == backwardMostMove) {
16844 // [HGM] perhaps we should always do it like this, and forget the above?
16845 if((signed char)boards[move][EP_STATUS] >= 0) {
16846 *p++ = boards[move][EP_STATUS] + AAA;
16847 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16858 /* [HGM] find reversible plies */
16859 { int i = 0, j=move;
16861 if (appData.debugMode) { int k;
16862 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16863 for(k=backwardMostMove; k<=forwardMostMove; k++)
16864 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16868 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16869 if( j == backwardMostMove ) i += initialRulePlies;
16870 sprintf(p, "%d ", i);
16871 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16873 /* Fullmove number */
16874 sprintf(p, "%d", (move / 2) + 1);
16876 return StrSave(buf);
16880 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16884 int emptycount, virgin[BOARD_FILES];
16889 /* [HGM] by default clear Crazyhouse holdings, if present */
16890 if(gameInfo.holdingsWidth) {
16891 for(i=0; i<BOARD_HEIGHT; i++) {
16892 board[i][0] = EmptySquare; /* black holdings */
16893 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16894 board[i][1] = (ChessSquare) 0; /* black counts */
16895 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16899 /* Piece placement data */
16900 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16903 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16904 if (*p == '/') p++;
16905 emptycount = gameInfo.boardWidth - j;
16906 while (emptycount--)
16907 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16909 #if(BOARD_FILES >= 10)
16910 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16911 p++; emptycount=10;
16912 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16913 while (emptycount--)
16914 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16916 } else if (isdigit(*p)) {
16917 emptycount = *p++ - '0';
16918 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16919 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16920 while (emptycount--)
16921 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16922 } else if (*p == '+' || isalpha(*p)) {
16923 if (j >= gameInfo.boardWidth) return FALSE;
16925 piece = CharToPiece(*++p);
16926 if(piece == EmptySquare) return FALSE; /* unknown piece */
16927 piece = (ChessSquare) (PROMOTED piece ); p++;
16928 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16929 } else piece = CharToPiece(*p++);
16931 if(piece==EmptySquare) return FALSE; /* unknown piece */
16932 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16933 piece = (ChessSquare) (PROMOTED piece);
16934 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16937 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16943 while (*p == '/' || *p == ' ') p++;
16945 /* [HGM] look for Crazyhouse holdings here */
16946 while(*p==' ') p++;
16947 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16949 if(*p == '-' ) p++; /* empty holdings */ else {
16950 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16951 /* if we would allow FEN reading to set board size, we would */
16952 /* have to add holdings and shift the board read so far here */
16953 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16955 if((int) piece >= (int) BlackPawn ) {
16956 i = (int)piece - (int)BlackPawn;
16957 i = PieceToNumber((ChessSquare)i);
16958 if( i >= gameInfo.holdingsSize ) return FALSE;
16959 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16960 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16962 i = (int)piece - (int)WhitePawn;
16963 i = PieceToNumber((ChessSquare)i);
16964 if( i >= gameInfo.holdingsSize ) return FALSE;
16965 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16966 board[i][BOARD_WIDTH-2]++; /* black holdings */
16973 while(*p == ' ') p++;
16977 if(appData.colorNickNames) {
16978 if( c == appData.colorNickNames[0] ) c = 'w'; else
16979 if( c == appData.colorNickNames[1] ) c = 'b';
16983 *blackPlaysFirst = FALSE;
16986 *blackPlaysFirst = TRUE;
16992 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16993 /* return the extra info in global variiables */
16995 /* set defaults in case FEN is incomplete */
16996 board[EP_STATUS] = EP_UNKNOWN;
16997 for(i=0; i<nrCastlingRights; i++ ) {
16998 board[CASTLING][i] =
16999 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17000 } /* assume possible unless obviously impossible */
17001 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17002 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17003 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17004 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17005 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17006 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17007 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17008 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17011 while(*p==' ') p++;
17012 if(nrCastlingRights) {
17013 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17014 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17015 /* castling indicator present, so default becomes no castlings */
17016 for(i=0; i<nrCastlingRights; i++ ) {
17017 board[CASTLING][i] = NoRights;
17020 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17021 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17022 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17023 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17024 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17026 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17027 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17028 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17030 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17031 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17032 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17033 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17034 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17035 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17038 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17039 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17040 board[CASTLING][2] = whiteKingFile;
17041 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17042 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17045 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17046 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17047 board[CASTLING][2] = whiteKingFile;
17048 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17049 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17052 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17053 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17054 board[CASTLING][5] = blackKingFile;
17055 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17056 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17059 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17060 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17061 board[CASTLING][5] = blackKingFile;
17062 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17063 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17066 default: /* FRC castlings */
17067 if(c >= 'a') { /* black rights */
17068 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17069 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17070 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17071 if(i == BOARD_RGHT) break;
17072 board[CASTLING][5] = i;
17074 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17075 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17077 board[CASTLING][3] = c;
17079 board[CASTLING][4] = c;
17080 } else { /* white rights */
17081 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17082 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17083 if(board[0][i] == WhiteKing) break;
17084 if(i == BOARD_RGHT) break;
17085 board[CASTLING][2] = i;
17086 c -= AAA - 'a' + 'A';
17087 if(board[0][c] >= WhiteKing) break;
17089 board[CASTLING][0] = c;
17091 board[CASTLING][1] = c;
17095 for(i=0; i<nrCastlingRights; i++)
17096 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17097 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17098 if (appData.debugMode) {
17099 fprintf(debugFP, "FEN castling rights:");
17100 for(i=0; i<nrCastlingRights; i++)
17101 fprintf(debugFP, " %d", board[CASTLING][i]);
17102 fprintf(debugFP, "\n");
17105 while(*p==' ') p++;
17108 /* read e.p. field in games that know e.p. capture */
17109 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17110 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17112 p++; board[EP_STATUS] = EP_NONE;
17114 char c = *p++ - AAA;
17116 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17117 if(*p >= '0' && *p <='9') p++;
17118 board[EP_STATUS] = c;
17123 if(sscanf(p, "%d", &i) == 1) {
17124 FENrulePlies = i; /* 50-move ply counter */
17125 /* (The move number is still ignored) */
17132 EditPositionPasteFEN (char *fen)
17135 Board initial_position;
17137 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17138 DisplayError(_("Bad FEN position in clipboard"), 0);
17141 int savedBlackPlaysFirst = blackPlaysFirst;
17142 EditPositionEvent();
17143 blackPlaysFirst = savedBlackPlaysFirst;
17144 CopyBoard(boards[0], initial_position);
17145 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17146 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17147 DisplayBothClocks();
17148 DrawPosition(FALSE, boards[currentMove]);
17153 static char cseq[12] = "\\ ";
17156 set_cont_sequence (char *new_seq)
17161 // handle bad attempts to set the sequence
17163 return 0; // acceptable error - no debug
17165 len = strlen(new_seq);
17166 ret = (len > 0) && (len < sizeof(cseq));
17168 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17169 else if (appData.debugMode)
17170 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17175 reformat a source message so words don't cross the width boundary. internal
17176 newlines are not removed. returns the wrapped size (no null character unless
17177 included in source message). If dest is NULL, only calculate the size required
17178 for the dest buffer. lp argument indicats line position upon entry, and it's
17179 passed back upon exit.
17182 wrap (char *dest, char *src, int count, int width, int *lp)
17184 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17186 cseq_len = strlen(cseq);
17187 old_line = line = *lp;
17188 ansi = len = clen = 0;
17190 for (i=0; i < count; i++)
17192 if (src[i] == '\033')
17195 // if we hit the width, back up
17196 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17198 // store i & len in case the word is too long
17199 old_i = i, old_len = len;
17201 // find the end of the last word
17202 while (i && src[i] != ' ' && src[i] != '\n')
17208 // word too long? restore i & len before splitting it
17209 if ((old_i-i+clen) >= width)
17216 if (i && src[i-1] == ' ')
17219 if (src[i] != ' ' && src[i] != '\n')
17226 // now append the newline and continuation sequence
17231 strncpy(dest+len, cseq, cseq_len);
17239 dest[len] = src[i];
17243 if (src[i] == '\n')
17248 if (dest && appData.debugMode)
17250 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17251 count, width, line, len, *lp);
17252 show_bytes(debugFP, src, count);
17253 fprintf(debugFP, "\ndest: ");
17254 show_bytes(debugFP, dest, len);
17255 fprintf(debugFP, "\n");
17257 *lp = dest ? line : old_line;
17262 // [HGM] vari: routines for shelving variations
17263 Boolean modeRestore = FALSE;
17266 PushInner (int firstMove, int lastMove)
17268 int i, j, nrMoves = lastMove - firstMove;
17270 // push current tail of game on stack
17271 savedResult[storedGames] = gameInfo.result;
17272 savedDetails[storedGames] = gameInfo.resultDetails;
17273 gameInfo.resultDetails = NULL;
17274 savedFirst[storedGames] = firstMove;
17275 savedLast [storedGames] = lastMove;
17276 savedFramePtr[storedGames] = framePtr;
17277 framePtr -= nrMoves; // reserve space for the boards
17278 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17279 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17280 for(j=0; j<MOVE_LEN; j++)
17281 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17282 for(j=0; j<2*MOVE_LEN; j++)
17283 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17284 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17285 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17286 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17287 pvInfoList[firstMove+i-1].depth = 0;
17288 commentList[framePtr+i] = commentList[firstMove+i];
17289 commentList[firstMove+i] = NULL;
17293 forwardMostMove = firstMove; // truncate game so we can start variation
17297 PushTail (int firstMove, int lastMove)
17299 if(appData.icsActive) { // only in local mode
17300 forwardMostMove = currentMove; // mimic old ICS behavior
17303 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17305 PushInner(firstMove, lastMove);
17306 if(storedGames == 1) GreyRevert(FALSE);
17307 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17311 PopInner (Boolean annotate)
17314 char buf[8000], moveBuf[20];
17316 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17317 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17318 nrMoves = savedLast[storedGames] - currentMove;
17321 if(!WhiteOnMove(currentMove))
17322 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17323 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17324 for(i=currentMove; i<forwardMostMove; i++) {
17326 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17327 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17328 strcat(buf, moveBuf);
17329 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17330 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17334 for(i=1; i<=nrMoves; i++) { // copy last variation back
17335 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17336 for(j=0; j<MOVE_LEN; j++)
17337 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17338 for(j=0; j<2*MOVE_LEN; j++)
17339 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17340 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17341 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17342 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17343 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17344 commentList[currentMove+i] = commentList[framePtr+i];
17345 commentList[framePtr+i] = NULL;
17347 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17348 framePtr = savedFramePtr[storedGames];
17349 gameInfo.result = savedResult[storedGames];
17350 if(gameInfo.resultDetails != NULL) {
17351 free(gameInfo.resultDetails);
17353 gameInfo.resultDetails = savedDetails[storedGames];
17354 forwardMostMove = currentMove + nrMoves;
17358 PopTail (Boolean annotate)
17360 if(appData.icsActive) return FALSE; // only in local mode
17361 if(!storedGames) return FALSE; // sanity
17362 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17364 PopInner(annotate);
17365 if(currentMove < forwardMostMove) ForwardEvent(); else
17366 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17368 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17374 { // remove all shelved variations
17376 for(i=0; i<storedGames; i++) {
17377 if(savedDetails[i])
17378 free(savedDetails[i]);
17379 savedDetails[i] = NULL;
17381 for(i=framePtr; i<MAX_MOVES; i++) {
17382 if(commentList[i]) free(commentList[i]);
17383 commentList[i] = NULL;
17385 framePtr = MAX_MOVES-1;
17390 LoadVariation (int index, char *text)
17391 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17392 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17393 int level = 0, move;
17395 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17396 // first find outermost bracketing variation
17397 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17398 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17399 if(*p == '{') wait = '}'; else
17400 if(*p == '[') wait = ']'; else
17401 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17402 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17404 if(*p == wait) wait = NULLCHAR; // closing ]} found
17407 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17408 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17409 end[1] = NULLCHAR; // clip off comment beyond variation
17410 ToNrEvent(currentMove-1);
17411 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17412 // kludge: use ParsePV() to append variation to game
17413 move = currentMove;
17414 ParsePV(start, TRUE, TRUE);
17415 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17416 ClearPremoveHighlights();
17418 ToNrEvent(currentMove+1);