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 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 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176 /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188 char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190 int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
398 flags &= ~F_ALL_CASTLE_OK;
406 FILE *gameFileFP, *debugFP;
409 [AS] Note: sometimes, the sscanf() function is used to parse the input
410 into a fixed-size buffer. Because of this, we must be prepared to
411 receive strings as long as the size of the input buffer, which is currently
412 set to 4K for Windows and 8K for the rest.
413 So, we must either allocate sufficiently large buffers here, or
414 reduce the size of the input buffer in the input reading part.
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
421 ChessProgramState first, second, pairing;
423 /* premove variables */
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
464 int have_sent_ICS_logon = 0;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 Boolean adjustedClock;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
478 /* animateTraining preserves the state of appData.animate
479 * when Training mode is activated. This allows the
480 * response to be animated when appData.animate == TRUE and
481 * appData.animateDragging == TRUE.
483 Boolean animateTraining;
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char initialRights[BOARD_FILES];
493 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int initialRulePlies, FENrulePlies;
495 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
516 ChessSquare FIDEArray[2][BOARD_FILES] = {
517 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520 BlackKing, BlackBishop, BlackKnight, BlackRook }
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527 BlackKing, BlackKing, BlackKnight, BlackRook }
530 ChessSquare KnightmateArray[2][BOARD_FILES] = {
531 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533 { BlackRook, BlackMan, BlackBishop, BlackQueen,
534 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackMan, BlackFerz,
562 BlackKing, BlackMan, BlackKnight, BlackRook }
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GrandArray[2][BOARD_FILES] = {
603 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
604 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
605 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
606 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
610 ChessSquare GothicArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
612 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
614 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 #define GothicArray CapablancaArray
621 ChessSquare FalconArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
623 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
625 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
628 #define FalconArray CapablancaArray
631 #else // !(BOARD_FILES>=10)
632 #define XiangqiPosition FIDEArray
633 #define CapablancaArray FIDEArray
634 #define GothicArray FIDEArray
635 #define GreatArray FIDEArray
636 #endif // !(BOARD_FILES>=10)
638 #if (BOARD_FILES>=12)
639 ChessSquare CourierArray[2][BOARD_FILES] = {
640 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
641 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
642 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
643 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
645 #else // !(BOARD_FILES>=12)
646 #define CourierArray CapablancaArray
647 #endif // !(BOARD_FILES>=12)
650 Board initialPosition;
653 /* Convert str to a rating. Checks for special cases of "----",
655 "++++", etc. Also strips ()'s */
657 string_to_rating(str)
660 while(*str && !isdigit(*str)) ++str;
662 return 0; /* One of the special "no rating" cases */
670 /* Init programStats */
671 programStats.movelist[0] = 0;
672 programStats.depth = 0;
673 programStats.nr_moves = 0;
674 programStats.moves_left = 0;
675 programStats.nodes = 0;
676 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
677 programStats.score = 0;
678 programStats.got_only_move = 0;
679 programStats.got_fail = 0;
680 programStats.line_is_book = 0;
685 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
686 if (appData.firstPlaysBlack) {
687 first.twoMachinesColor = "black\n";
688 second.twoMachinesColor = "white\n";
690 first.twoMachinesColor = "white\n";
691 second.twoMachinesColor = "black\n";
694 first.other = &second;
695 second.other = &first;
698 if(appData.timeOddsMode) {
699 norm = appData.timeOdds[0];
700 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
702 first.timeOdds = appData.timeOdds[0]/norm;
703 second.timeOdds = appData.timeOdds[1]/norm;
706 if(programVersion) free(programVersion);
707 if (appData.noChessProgram) {
708 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
709 sprintf(programVersion, "%s", PACKAGE_STRING);
711 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
712 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
713 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
718 UnloadEngine(ChessProgramState *cps)
720 /* Kill off first chess program */
721 if (cps->isr != NULL)
722 RemoveInputSource(cps->isr);
725 if (cps->pr != NoProc) {
727 DoSleep( appData.delayBeforeQuit );
728 SendToProgram("quit\n", cps);
729 DoSleep( appData.delayAfterQuit );
730 DestroyChildProcess(cps->pr, cps->useSigterm);
733 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 ClearOptions(ChessProgramState *cps)
740 cps->nrOptions = cps->comboCnt = 0;
741 for(i=0; i<MAX_OPTIONS; i++) {
742 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
743 cps->option[i].textValue = 0;
747 char *engineNames[] = {
753 InitEngine(ChessProgramState *cps, int n)
754 { // [HGM] all engine initialiation put in a function that does one engine
758 cps->which = engineNames[n];
759 cps->maybeThinking = FALSE;
763 cps->sendDrawOffers = 1;
765 cps->program = appData.chessProgram[n];
766 cps->host = appData.host[n];
767 cps->dir = appData.directory[n];
768 cps->initString = appData.engInitString[n];
769 cps->computerString = appData.computerString[n];
770 cps->useSigint = TRUE;
771 cps->useSigterm = TRUE;
772 cps->reuse = appData.reuse[n];
773 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
774 cps->useSetboard = FALSE;
776 cps->usePing = FALSE;
779 cps->usePlayother = FALSE;
780 cps->useColors = TRUE;
781 cps->useUsermove = FALSE;
782 cps->sendICS = FALSE;
783 cps->sendName = appData.icsActive;
784 cps->sdKludge = FALSE;
785 cps->stKludge = FALSE;
786 TidyProgramName(cps->program, cps->host, cps->tidy);
788 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
789 cps->analysisSupport = 2; /* detect */
790 cps->analyzing = FALSE;
791 cps->initDone = FALSE;
793 /* New features added by Tord: */
794 cps->useFEN960 = FALSE;
795 cps->useOOCastle = TRUE;
796 /* End of new features added by Tord. */
797 cps->fenOverride = appData.fenOverride[n];
799 /* [HGM] time odds: set factor for each machine */
800 cps->timeOdds = appData.timeOdds[n];
802 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
803 cps->accumulateTC = appData.accumulateTC[n];
804 cps->maxNrOfSessions = 1;
809 cps->supportsNPS = UNKNOWN;
810 cps->memSize = FALSE;
811 cps->maxCores = FALSE;
812 cps->egtFormats[0] = NULLCHAR;
815 cps->optionSettings = appData.engOptions[n];
817 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
818 cps->isUCI = appData.isUCI[n]; /* [AS] */
819 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
821 if (appData.protocolVersion[n] > PROTOVER
822 || appData.protocolVersion[n] < 1)
827 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
828 appData.protocolVersion[n]);
829 if( (len > MSG_SIZ) && appData.debugMode )
830 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
832 DisplayFatalError(buf, 0, 2);
836 cps->protocolVersion = appData.protocolVersion[n];
839 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
842 ChessProgramState *savCps;
848 if(WaitForEngine(savCps, LoadEngine)) return;
849 CommonEngineInit(); // recalculate time odds
850 if(gameInfo.variant != StringToVariant(appData.variant)) {
851 // we changed variant when loading the engine; this forces us to reset
852 Reset(TRUE, savCps != &first);
853 EditGameEvent(); // for consistency with other path, as Reset changes mode
855 InitChessProgram(savCps, FALSE);
856 SendToProgram("force\n", savCps);
857 DisplayMessage("", "");
858 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
859 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
865 ReplaceEngine(ChessProgramState *cps, int n)
869 appData.noChessProgram = FALSE;
870 appData.clockMode = TRUE;
873 if(n) return; // only startup first engine immediately; second can wait
874 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
878 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
879 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
881 static char resetOptions[] =
882 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
883 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
884 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
887 Load(ChessProgramState *cps, int i)
889 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
891 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
894 ParseArgsFromString(buf);
896 ReplaceEngine(cps, i);
900 while(q = strchr(p, SLASH)) p = q+1;
901 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902 if(engineDir[0] != NULLCHAR)
903 appData.directory[i] = engineDir;
904 else if(p != engineName) { // derive directory from engine path, when not given
906 appData.directory[i] = strdup(engineName);
908 } else appData.directory[i] = ".";
910 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911 snprintf(command, MSG_SIZ, "%s %s", p, params);
914 appData.chessProgram[i] = strdup(p);
915 appData.isUCI[i] = isUCI;
916 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917 appData.hasOwnBookUCI[i] = hasBook;
918 if(!nickName[0]) useNick = FALSE;
919 if(useNick) ASSIGN(appData.pgnName[i], nickName);
923 q = firstChessProgramNames;
924 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927 quote, p, quote, appData.directory[i],
928 useNick ? " -fn \"" : "",
929 useNick ? nickName : "",
931 v1 ? " -firstProtocolVersion 1" : "",
932 hasBook ? "" : " -fNoOwnBookUCI",
933 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934 storeVariant ? " -variant " : "",
935 storeVariant ? VariantName(gameInfo.variant) : "");
936 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
940 ReplaceEngine(cps, i);
946 int matched, min, sec;
948 * Parse timeControl resource
950 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951 appData.movesPerSession)) {
953 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954 DisplayFatalError(buf, 0, 2);
958 * Parse searchTime resource
960 if (*appData.searchTime != NULLCHAR) {
961 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963 searchTime = min * 60;
964 } else if (matched == 2) {
965 searchTime = min * 60 + sec;
968 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969 DisplayFatalError(buf, 0, 2);
978 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981 GetTimeMark(&programStartTime);
982 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983 appData.seedBase = random() + (random()<<15);
984 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
987 programStats.ok_to_send = 1;
988 programStats.seen_stat = 0;
991 * Initialize game list
997 * Internet chess server status
999 if (appData.icsActive) {
1000 appData.matchMode = FALSE;
1001 appData.matchGames = 0;
1003 appData.noChessProgram = !appData.zippyPlay;
1005 appData.zippyPlay = FALSE;
1006 appData.zippyTalk = FALSE;
1007 appData.noChessProgram = TRUE;
1009 if (*appData.icsHelper != NULLCHAR) {
1010 appData.useTelnet = TRUE;
1011 appData.telnetProgram = appData.icsHelper;
1014 appData.zippyTalk = appData.zippyPlay = FALSE;
1017 /* [AS] Initialize pv info list [HGM] and game state */
1021 for( i=0; i<=framePtr; i++ ) {
1022 pvInfoList[i].depth = -1;
1023 boards[i][EP_STATUS] = EP_NONE;
1024 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1030 /* [AS] Adjudication threshold */
1031 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033 InitEngine(&first, 0);
1034 InitEngine(&second, 1);
1037 pairing.which = "pairing"; // pairing engine
1038 pairing.pr = NoProc;
1040 pairing.program = appData.pairingEngine;
1041 pairing.host = "localhost";
1044 if (appData.icsActive) {
1045 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1046 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047 appData.clockMode = FALSE;
1048 first.sendTime = second.sendTime = 0;
1052 /* Override some settings from environment variables, for backward
1053 compatibility. Unfortunately it's not feasible to have the env
1054 vars just set defaults, at least in xboard. Ugh.
1056 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1061 if (!appData.icsActive) {
1065 /* Check for variants that are supported only in ICS mode,
1066 or not at all. Some that are accepted here nevertheless
1067 have bugs; see comments below.
1069 VariantClass variant = StringToVariant(appData.variant);
1071 case VariantBughouse: /* need four players and two boards */
1072 case VariantKriegspiel: /* need to hide pieces and move details */
1073 /* case VariantFischeRandom: (Fabien: moved below) */
1074 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075 if( (len > MSG_SIZ) && appData.debugMode )
1076 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078 DisplayFatalError(buf, 0, 2);
1081 case VariantUnknown:
1082 case VariantLoadable:
1092 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093 if( (len > MSG_SIZ) && appData.debugMode )
1094 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096 DisplayFatalError(buf, 0, 2);
1099 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1100 case VariantFairy: /* [HGM] TestLegality definitely off! */
1101 case VariantGothic: /* [HGM] should work */
1102 case VariantCapablanca: /* [HGM] should work */
1103 case VariantCourier: /* [HGM] initial forced moves not implemented */
1104 case VariantShogi: /* [HGM] could still mate with pawn drop */
1105 case VariantKnightmate: /* [HGM] should work */
1106 case VariantCylinder: /* [HGM] untested */
1107 case VariantFalcon: /* [HGM] untested */
1108 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109 offboard interposition not understood */
1110 case VariantNormal: /* definitely works! */
1111 case VariantWildCastle: /* pieces not automatically shuffled */
1112 case VariantNoCastle: /* pieces not automatically shuffled */
1113 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114 case VariantLosers: /* should work except for win condition,
1115 and doesn't know captures are mandatory */
1116 case VariantSuicide: /* should work except for win condition,
1117 and doesn't know captures are mandatory */
1118 case VariantGiveaway: /* should work except for win condition,
1119 and doesn't know captures are mandatory */
1120 case VariantTwoKings: /* should work */
1121 case VariantAtomic: /* should work except for win condition */
1122 case Variant3Check: /* should work except for win condition */
1123 case VariantShatranj: /* should work except for all win conditions */
1124 case VariantMakruk: /* should work except for draw countdown */
1125 case VariantBerolina: /* might work if TestLegality is off */
1126 case VariantCapaRandom: /* should work */
1127 case VariantJanus: /* should work */
1128 case VariantSuper: /* experimental */
1129 case VariantGreat: /* experimental, requires legality testing to be off */
1130 case VariantSChess: /* S-Chess, should work */
1131 case VariantGrand: /* should work */
1132 case VariantSpartan: /* should work */
1139 int NextIntegerFromString( char ** str, long * value )
1144 while( *s == ' ' || *s == '\t' ) {
1150 if( *s >= '0' && *s <= '9' ) {
1151 while( *s >= '0' && *s <= '9' ) {
1152 *value = *value * 10 + (*s - '0');
1164 int NextTimeControlFromString( char ** str, long * value )
1167 int result = NextIntegerFromString( str, &temp );
1170 *value = temp * 60; /* Minutes */
1171 if( **str == ':' ) {
1173 result = NextIntegerFromString( str, &temp );
1174 *value += temp; /* Seconds */
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183 int result = -1, type = 0; long temp, temp2;
1185 if(**str != ':') return -1; // old params remain in force!
1187 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188 if( NextIntegerFromString( str, &temp ) ) return -1;
1189 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1192 /* time only: incremental or sudden-death time control */
1193 if(**str == '+') { /* increment follows; read it */
1195 if(**str == '!') type = *(*str)++; // Bronstein TC
1196 if(result = NextIntegerFromString( str, &temp2)) return -1;
1197 *inc = temp2 * 1000;
1198 if(**str == '.') { // read fraction of increment
1199 char *start = ++(*str);
1200 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202 while(start++ < *str) temp2 /= 10;
1206 *moves = 0; *tc = temp * 1000; *incType = type;
1210 (*str)++; /* classical time control */
1211 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 { /* [HGM] get time to add from the multi-session time-control string */
1224 int incType, moves=1; /* kludge to force reading of first session */
1225 long time, increment;
1228 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234 if(movenr == -1) return time; /* last move before new session */
1235 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237 if(!moves) return increment; /* current session is incremental */
1238 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239 } while(movenr >= -1); /* try again for next session */
1241 return 0; // no new time quota on this move
1245 ParseTimeControl(tc, ti, mps)
1252 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1255 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1261 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1266 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268 snprintf(buf, MSG_SIZ, ":%s", mytc);
1270 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1277 /* Parse second time control */
1280 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1288 timeControl_2 = tc2 * 1000;
1298 timeControl = tc1 * 1000;
1301 timeIncrement = ti * 1000; /* convert to ms */
1302 movesPerSession = 0;
1305 movesPerSession = mps;
1313 if (appData.debugMode) {
1314 fprintf(debugFP, "%s\n", programVersion);
1317 set_cont_sequence(appData.wrapContSeq);
1318 if (appData.matchGames > 0) {
1319 appData.matchMode = TRUE;
1320 } else if (appData.matchMode) {
1321 appData.matchGames = 1;
1323 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324 appData.matchGames = appData.sameColorGames;
1325 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1330 if (appData.noChessProgram || first.protocolVersion == 1) {
1333 /* kludge: allow timeout for initial "feature" commands */
1335 DisplayMessage("", _("Starting chess program"));
1336 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1341 CalculateIndex(int index, int gameNr)
1342 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344 if(index > 0) return index; // fixed nmber
1345 if(index == 0) return 1;
1346 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1352 LoadGameOrPosition(int gameNr)
1353 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354 if (*appData.loadGameFile != NULLCHAR) {
1355 if (!LoadGameFromFile(appData.loadGameFile,
1356 CalculateIndex(appData.loadGameIndex, gameNr),
1357 appData.loadGameFile, FALSE)) {
1358 DisplayFatalError(_("Bad game file"), 0, 1);
1361 } else if (*appData.loadPositionFile != NULLCHAR) {
1362 if (!LoadPositionFromFile(appData.loadPositionFile,
1363 CalculateIndex(appData.loadPositionIndex, gameNr),
1364 appData.loadPositionFile)) {
1365 DisplayFatalError(_("Bad position file"), 0, 1);
1373 ReserveGame(int gameNr, char resChar)
1375 FILE *tf = fopen(appData.tourneyFile, "r+");
1376 char *p, *q, c, buf[MSG_SIZ];
1377 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378 safeStrCpy(buf, lastMsg, MSG_SIZ);
1379 DisplayMessage(_("Pick new game"), "");
1380 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381 ParseArgsFromFile(tf);
1382 p = q = appData.results;
1383 if(appData.debugMode) {
1384 char *r = appData.participants;
1385 fprintf(debugFP, "results = '%s'\n", p);
1386 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387 fprintf(debugFP, "\n");
1389 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392 safeStrCpy(q, p, strlen(p) + 2);
1393 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1399 fseek(tf, -(strlen(p)+4), SEEK_END);
1401 if(c != '"') // depending on DOS or Unix line endings we can be one off
1402 fseek(tf, -(strlen(p)+2), SEEK_END);
1403 else fseek(tf, -(strlen(p)+3), SEEK_END);
1404 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405 DisplayMessage(buf, "");
1406 free(p); appData.results = q;
1407 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409 UnloadEngine(&first); // next game belongs to other pairing;
1410 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1415 MatchEvent(int mode)
1416 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418 if(matchMode) { // already in match mode: switch it off
1420 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1423 // if(gameMode != BeginningOfGame) {
1424 // DisplayError(_("You can only start a match from the initial position."), 0);
1428 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429 /* Set up machine vs. machine match */
1431 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432 if(appData.tourneyFile[0]) {
1434 if(nextGame > appData.matchGames) {
1436 if(strchr(appData.results, '*') == NULL) {
1438 appData.tourneyCycles++;
1439 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441 NextTourneyGame(-1, &dummy);
1443 if(nextGame <= appData.matchGames) {
1444 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446 ScheduleDelayedEvent(NextMatchGame, 10000);
1451 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452 DisplayError(buf, 0);
1453 appData.tourneyFile[0] = 0;
1457 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1458 DisplayFatalError(_("Can't have a match with no chess programs"),
1463 matchGame = roundNr = 1;
1464 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1469 InitBackEnd3 P((void))
1471 GameMode initialMode;
1475 InitChessProgram(&first, startedFromSetupPosition);
1477 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1478 free(programVersion);
1479 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1483 if (appData.icsActive) {
1485 /* [DM] Make a console window if needed [HGM] merged ifs */
1491 if (*appData.icsCommPort != NULLCHAR)
1492 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493 appData.icsCommPort);
1495 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496 appData.icsHost, appData.icsPort);
1498 if( (len > MSG_SIZ) && appData.debugMode )
1499 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501 DisplayFatalError(buf, err, 1);
1506 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511 } else if (appData.noChessProgram) {
1517 if (*appData.cmailGameName != NULLCHAR) {
1519 OpenLoopback(&cmailPR);
1521 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1525 DisplayMessage("", "");
1526 if (StrCaseCmp(appData.initialMode, "") == 0) {
1527 initialMode = BeginningOfGame;
1528 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1534 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535 initialMode = TwoMachinesPlay;
1536 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537 initialMode = AnalyzeFile;
1538 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539 initialMode = AnalyzeMode;
1540 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541 initialMode = MachinePlaysWhite;
1542 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543 initialMode = MachinePlaysBlack;
1544 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545 initialMode = EditGame;
1546 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547 initialMode = EditPosition;
1548 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549 initialMode = Training;
1551 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552 if( (len > MSG_SIZ) && appData.debugMode )
1553 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555 DisplayFatalError(buf, 0, 2);
1559 if (appData.matchMode) {
1560 if(appData.tourneyFile[0]) { // start tourney from command line
1562 if(f = fopen(appData.tourneyFile, "r")) {
1563 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565 appData.clockMode = TRUE;
1567 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1570 } else if (*appData.cmailGameName != NULLCHAR) {
1571 /* Set up cmail mode */
1572 ReloadCmailMsgEvent(TRUE);
1574 /* Set up other modes */
1575 if (initialMode == AnalyzeFile) {
1576 if (*appData.loadGameFile == NULLCHAR) {
1577 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1581 if (*appData.loadGameFile != NULLCHAR) {
1582 (void) LoadGameFromFile(appData.loadGameFile,
1583 appData.loadGameIndex,
1584 appData.loadGameFile, TRUE);
1585 } else if (*appData.loadPositionFile != NULLCHAR) {
1586 (void) LoadPositionFromFile(appData.loadPositionFile,
1587 appData.loadPositionIndex,
1588 appData.loadPositionFile);
1589 /* [HGM] try to make self-starting even after FEN load */
1590 /* to allow automatic setup of fairy variants with wtm */
1591 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592 gameMode = BeginningOfGame;
1593 setboardSpoiledMachineBlack = 1;
1595 /* [HGM] loadPos: make that every new game uses the setup */
1596 /* from file as long as we do not switch variant */
1597 if(!blackPlaysFirst) {
1598 startedFromPositionFile = TRUE;
1599 CopyBoard(filePosition, boards[0]);
1602 if (initialMode == AnalyzeMode) {
1603 if (appData.noChessProgram) {
1604 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1607 if (appData.icsActive) {
1608 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1612 } else if (initialMode == AnalyzeFile) {
1613 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614 ShowThinkingEvent();
1616 AnalysisPeriodicEvent(1);
1617 } else if (initialMode == MachinePlaysWhite) {
1618 if (appData.noChessProgram) {
1619 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1623 if (appData.icsActive) {
1624 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1628 MachineWhiteEvent();
1629 } else if (initialMode == MachinePlaysBlack) {
1630 if (appData.noChessProgram) {
1631 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1635 if (appData.icsActive) {
1636 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1640 MachineBlackEvent();
1641 } else if (initialMode == TwoMachinesPlay) {
1642 if (appData.noChessProgram) {
1643 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1647 if (appData.icsActive) {
1648 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1653 } else if (initialMode == EditGame) {
1655 } else if (initialMode == EditPosition) {
1656 EditPositionEvent();
1657 } else if (initialMode == Training) {
1658 if (*appData.loadGameFile == NULLCHAR) {
1659 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1668 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1670 DisplayBook(current+1);
1672 MoveHistorySet( movelist, first, last, current, pvInfoList );
1674 EvalGraphSet( first, last, current, pvInfoList );
1676 MakeEngineOutputTitle();
1680 * Establish will establish a contact to a remote host.port.
1681 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1682 * used to talk to the host.
1683 * Returns 0 if okay, error code if not.
1690 if (*appData.icsCommPort != NULLCHAR) {
1691 /* Talk to the host through a serial comm port */
1692 return OpenCommPort(appData.icsCommPort, &icsPR);
1694 } else if (*appData.gateway != NULLCHAR) {
1695 if (*appData.remoteShell == NULLCHAR) {
1696 /* Use the rcmd protocol to run telnet program on a gateway host */
1697 snprintf(buf, sizeof(buf), "%s %s %s",
1698 appData.telnetProgram, appData.icsHost, appData.icsPort);
1699 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1702 /* Use the rsh program to run telnet program on a gateway host */
1703 if (*appData.remoteUser == NULLCHAR) {
1704 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1705 appData.gateway, appData.telnetProgram,
1706 appData.icsHost, appData.icsPort);
1708 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1709 appData.remoteShell, appData.gateway,
1710 appData.remoteUser, appData.telnetProgram,
1711 appData.icsHost, appData.icsPort);
1713 return StartChildProcess(buf, "", &icsPR);
1716 } else if (appData.useTelnet) {
1717 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1720 /* TCP socket interface differs somewhat between
1721 Unix and NT; handle details in the front end.
1723 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1727 void EscapeExpand(char *p, char *q)
1728 { // [HGM] initstring: routine to shape up string arguments
1729 while(*p++ = *q++) if(p[-1] == '\\')
1731 case 'n': p[-1] = '\n'; break;
1732 case 'r': p[-1] = '\r'; break;
1733 case 't': p[-1] = '\t'; break;
1734 case '\\': p[-1] = '\\'; break;
1735 case 0: *p = 0; return;
1736 default: p[-1] = q[-1]; break;
1741 show_bytes(fp, buf, count)
1747 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1748 fprintf(fp, "\\%03o", *buf & 0xff);
1757 /* Returns an errno value */
1759 OutputMaybeTelnet(pr, message, count, outError)
1765 char buf[8192], *p, *q, *buflim;
1766 int left, newcount, outcount;
1768 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1769 *appData.gateway != NULLCHAR) {
1770 if (appData.debugMode) {
1771 fprintf(debugFP, ">ICS: ");
1772 show_bytes(debugFP, message, count);
1773 fprintf(debugFP, "\n");
1775 return OutputToProcess(pr, message, count, outError);
1778 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1785 if (appData.debugMode) {
1786 fprintf(debugFP, ">ICS: ");
1787 show_bytes(debugFP, buf, newcount);
1788 fprintf(debugFP, "\n");
1790 outcount = OutputToProcess(pr, buf, newcount, outError);
1791 if (outcount < newcount) return -1; /* to be sure */
1798 } else if (((unsigned char) *p) == TN_IAC) {
1799 *q++ = (char) TN_IAC;
1806 if (appData.debugMode) {
1807 fprintf(debugFP, ">ICS: ");
1808 show_bytes(debugFP, buf, newcount);
1809 fprintf(debugFP, "\n");
1811 outcount = OutputToProcess(pr, buf, newcount, outError);
1812 if (outcount < newcount) return -1; /* to be sure */
1817 read_from_player(isr, closure, message, count, error)
1824 int outError, outCount;
1825 static int gotEof = 0;
1827 /* Pass data read from player on to ICS */
1830 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1831 if (outCount < count) {
1832 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1834 } else if (count < 0) {
1835 RemoveInputSource(isr);
1836 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1837 } else if (gotEof++ > 0) {
1838 RemoveInputSource(isr);
1839 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1845 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1846 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1847 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1848 SendToICS("date\n");
1849 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1852 /* added routine for printf style output to ics */
1853 void ics_printf(char *format, ...)
1855 char buffer[MSG_SIZ];
1858 va_start(args, format);
1859 vsnprintf(buffer, sizeof(buffer), format, args);
1860 buffer[sizeof(buffer)-1] = '\0';
1869 int count, outCount, outError;
1871 if (icsPR == NoProc) return;
1874 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1875 if (outCount < count) {
1876 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 /* This is used for sending logon scripts to the ICS. Sending
1881 without a delay causes problems when using timestamp on ICC
1882 (at least on my machine). */
1884 SendToICSDelayed(s,msdelay)
1888 int count, outCount, outError;
1890 if (icsPR == NoProc) return;
1893 if (appData.debugMode) {
1894 fprintf(debugFP, ">ICS: ");
1895 show_bytes(debugFP, s, count);
1896 fprintf(debugFP, "\n");
1898 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1900 if (outCount < count) {
1901 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1906 /* Remove all highlighting escape sequences in s
1907 Also deletes any suffix starting with '('
1910 StripHighlightAndTitle(s)
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1922 if (*s == '(' || *s == '[') {
1933 /* Remove all highlighting escape sequences in s */
1938 static char retbuf[MSG_SIZ];
1941 while (*s != NULLCHAR) {
1942 while (*s == '\033') {
1943 while (*s != NULLCHAR && !isalpha(*s)) s++;
1944 if (*s != NULLCHAR) s++;
1946 while (*s != NULLCHAR && *s != '\033') {
1954 char *variantNames[] = VARIANT_NAMES;
1959 return variantNames[v];
1963 /* Identify a variant from the strings the chess servers use or the
1964 PGN Variant tag names we use. */
1971 VariantClass v = VariantNormal;
1972 int i, found = FALSE;
1978 /* [HGM] skip over optional board-size prefixes */
1979 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981 while( *e++ != '_');
1984 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1988 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989 if (StrCaseStr(e, variantNames[i])) {
1990 v = (VariantClass) i;
1997 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998 || StrCaseStr(e, "wild/fr")
1999 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000 v = VariantFischeRandom;
2001 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002 (i = 1, p = StrCaseStr(e, "w"))) {
2004 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011 case 0: /* FICS only, actually */
2013 /* Castling legal even if K starts on d-file */
2014 v = VariantWildCastle;
2019 /* Castling illegal even if K & R happen to start in
2020 normal positions. */
2021 v = VariantNoCastle;
2034 /* Castling legal iff K & R start in normal positions */
2040 /* Special wilds for position setup; unclear what to do here */
2041 v = VariantLoadable;
2044 /* Bizarre ICC game */
2045 v = VariantTwoKings;
2048 v = VariantKriegspiel;
2054 v = VariantFischeRandom;
2057 v = VariantCrazyhouse;
2060 v = VariantBughouse;
2066 /* Not quite the same as FICS suicide! */
2067 v = VariantGiveaway;
2073 v = VariantShatranj;
2076 /* Temporary names for future ICC types. The name *will* change in
2077 the next xboard/WinBoard release after ICC defines it. */
2115 v = VariantCapablanca;
2118 v = VariantKnightmate;
2124 v = VariantCylinder;
2130 v = VariantCapaRandom;
2133 v = VariantBerolina;
2145 /* Found "wild" or "w" in the string but no number;
2146 must assume it's normal chess. */
2150 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151 if( (len > MSG_SIZ) && appData.debugMode )
2152 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2154 DisplayError(buf, 0);
2160 if (appData.debugMode) {
2161 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162 e, wnum, VariantName(v));
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171 advance *index beyond it, and set leftover_start to the new value of
2172 *index; else return FALSE. If pattern contains the character '*', it
2173 matches any sequence of characters not containing '\r', '\n', or the
2174 character following the '*' (if any), and the matched sequence(s) are
2175 copied into star_match.
2178 looking_at(buf, index, pattern)
2183 char *bufp = &buf[*index], *patternp = pattern;
2185 char *matchp = star_match[0];
2188 if (*patternp == NULLCHAR) {
2189 *index = leftover_start = bufp - buf;
2193 if (*bufp == NULLCHAR) return FALSE;
2194 if (*patternp == '*') {
2195 if (*bufp == *(patternp + 1)) {
2197 matchp = star_match[++star_count];
2201 } else if (*bufp == '\n' || *bufp == '\r') {
2203 if (*patternp == NULLCHAR)
2208 *matchp++ = *bufp++;
2212 if (*patternp != *bufp) return FALSE;
2219 SendToPlayer(data, length)
2223 int error, outCount;
2224 outCount = OutputToProcess(NoProc, data, length, &error);
2225 if (outCount < length) {
2226 DisplayFatalError(_("Error writing to display"), error, 1);
2231 PackHolding(packed, holding)
2243 switch (runlength) {
2254 sprintf(q, "%d", runlength);
2266 /* Telnet protocol requests from the front end */
2268 TelnetRequest(ddww, option)
2269 unsigned char ddww, option;
2271 unsigned char msg[3];
2272 int outCount, outError;
2274 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2276 if (appData.debugMode) {
2277 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2293 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2302 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2305 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2310 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2312 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319 if (!appData.icsActive) return;
2320 TelnetRequest(TN_DO, TN_ECHO);
2326 if (!appData.icsActive) return;
2327 TelnetRequest(TN_DONT, TN_ECHO);
2331 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2333 /* put the holdings sent to us by the server on the board holdings area */
2334 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2338 if(gameInfo.holdingsWidth < 2) return;
2339 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2340 return; // prevent overwriting by pre-board holdings
2342 if( (int)lowestPiece >= BlackPawn ) {
2345 holdingsStartRow = BOARD_HEIGHT-1;
2348 holdingsColumn = BOARD_WIDTH-1;
2349 countsColumn = BOARD_WIDTH-2;
2350 holdingsStartRow = 0;
2354 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2355 board[i][holdingsColumn] = EmptySquare;
2356 board[i][countsColumn] = (ChessSquare) 0;
2358 while( (p=*holdings++) != NULLCHAR ) {
2359 piece = CharToPiece( ToUpper(p) );
2360 if(piece == EmptySquare) continue;
2361 /*j = (int) piece - (int) WhitePawn;*/
2362 j = PieceToNumber(piece);
2363 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2364 if(j < 0) continue; /* should not happen */
2365 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2366 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2367 board[holdingsStartRow+j*direction][countsColumn]++;
2373 VariantSwitch(Board board, VariantClass newVariant)
2375 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2376 static Board oldBoard;
2378 startedFromPositionFile = FALSE;
2379 if(gameInfo.variant == newVariant) return;
2381 /* [HGM] This routine is called each time an assignment is made to
2382 * gameInfo.variant during a game, to make sure the board sizes
2383 * are set to match the new variant. If that means adding or deleting
2384 * holdings, we shift the playing board accordingly
2385 * This kludge is needed because in ICS observe mode, we get boards
2386 * of an ongoing game without knowing the variant, and learn about the
2387 * latter only later. This can be because of the move list we requested,
2388 * in which case the game history is refilled from the beginning anyway,
2389 * but also when receiving holdings of a crazyhouse game. In the latter
2390 * case we want to add those holdings to the already received position.
2394 if (appData.debugMode) {
2395 fprintf(debugFP, "Switch board from %s to %s\n",
2396 VariantName(gameInfo.variant), VariantName(newVariant));
2397 setbuf(debugFP, NULL);
2399 shuffleOpenings = 0; /* [HGM] shuffle */
2400 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2404 newWidth = 9; newHeight = 9;
2405 gameInfo.holdingsSize = 7;
2406 case VariantBughouse:
2407 case VariantCrazyhouse:
2408 newHoldingsWidth = 2; break;
2412 newHoldingsWidth = 2;
2413 gameInfo.holdingsSize = 8;
2416 case VariantCapablanca:
2417 case VariantCapaRandom:
2420 newHoldingsWidth = gameInfo.holdingsSize = 0;
2423 if(newWidth != gameInfo.boardWidth ||
2424 newHeight != gameInfo.boardHeight ||
2425 newHoldingsWidth != gameInfo.holdingsWidth ) {
2427 /* shift position to new playing area, if needed */
2428 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2429 for(i=0; i<BOARD_HEIGHT; i++)
2430 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2431 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433 for(i=0; i<newHeight; i++) {
2434 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2435 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2437 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2438 for(i=0; i<BOARD_HEIGHT; i++)
2439 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2440 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2443 gameInfo.boardWidth = newWidth;
2444 gameInfo.boardHeight = newHeight;
2445 gameInfo.holdingsWidth = newHoldingsWidth;
2446 gameInfo.variant = newVariant;
2447 InitDrawingSizes(-2, 0);
2448 } else gameInfo.variant = newVariant;
2449 CopyBoard(oldBoard, board); // remember correctly formatted board
2450 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2451 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2454 static int loggedOn = FALSE;
2456 /*-- Game start info cache: --*/
2458 char gs_kind[MSG_SIZ];
2459 static char player1Name[128] = "";
2460 static char player2Name[128] = "";
2461 static char cont_seq[] = "\n\\ ";
2462 static int player1Rating = -1;
2463 static int player2Rating = -1;
2464 /*----------------------------*/
2466 ColorClass curColor = ColorNormal;
2467 int suppressKibitz = 0;
2470 Boolean soughtPending = FALSE;
2471 Boolean seekGraphUp;
2472 #define MAX_SEEK_ADS 200
2474 char *seekAdList[MAX_SEEK_ADS];
2475 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2476 float tcList[MAX_SEEK_ADS];
2477 char colorList[MAX_SEEK_ADS];
2478 int nrOfSeekAds = 0;
2479 int minRating = 1010, maxRating = 2800;
2480 int hMargin = 10, vMargin = 20, h, w;
2481 extern int squareSize, lineGap;
2486 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2487 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2488 if(r < minRating+100 && r >=0 ) r = minRating+100;
2489 if(r > maxRating) r = maxRating;
2490 if(tc < 1.) tc = 1.;
2491 if(tc > 95.) tc = 95.;
2492 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2493 y = ((double)r - minRating)/(maxRating - minRating)
2494 * (h-vMargin-squareSize/8-1) + vMargin;
2495 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2496 if(strstr(seekAdList[i], " u ")) color = 1;
2497 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2498 !strstr(seekAdList[i], "bullet") &&
2499 !strstr(seekAdList[i], "blitz") &&
2500 !strstr(seekAdList[i], "standard") ) color = 2;
2501 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2502 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2506 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2508 char buf[MSG_SIZ], *ext = "";
2509 VariantClass v = StringToVariant(type);
2510 if(strstr(type, "wild")) {
2511 ext = type + 4; // append wild number
2512 if(v == VariantFischeRandom) type = "chess960"; else
2513 if(v == VariantLoadable) type = "setup"; else
2514 type = VariantName(v);
2516 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2517 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2518 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2519 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2520 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2521 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2522 seekNrList[nrOfSeekAds] = nr;
2523 zList[nrOfSeekAds] = 0;
2524 seekAdList[nrOfSeekAds++] = StrSave(buf);
2525 if(plot) PlotSeekAd(nrOfSeekAds-1);
2532 int x = xList[i], y = yList[i], d=squareSize/4, k;
2533 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2534 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2535 // now replot every dot that overlapped
2536 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2537 int xx = xList[k], yy = yList[k];
2538 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2539 DrawSeekDot(xx, yy, colorList[k]);
2544 RemoveSeekAd(int nr)
2547 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2549 if(seekAdList[i]) free(seekAdList[i]);
2550 seekAdList[i] = seekAdList[--nrOfSeekAds];
2551 seekNrList[i] = seekNrList[nrOfSeekAds];
2552 ratingList[i] = ratingList[nrOfSeekAds];
2553 colorList[i] = colorList[nrOfSeekAds];
2554 tcList[i] = tcList[nrOfSeekAds];
2555 xList[i] = xList[nrOfSeekAds];
2556 yList[i] = yList[nrOfSeekAds];
2557 zList[i] = zList[nrOfSeekAds];
2558 seekAdList[nrOfSeekAds] = NULL;
2564 MatchSoughtLine(char *line)
2566 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2567 int nr, base, inc, u=0; char dummy;
2569 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2572 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2573 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2574 // match: compact and save the line
2575 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2585 if(!seekGraphUp) return FALSE;
2586 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2587 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2589 DrawSeekBackground(0, 0, w, h);
2590 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2591 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2592 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2593 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2595 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2598 snprintf(buf, MSG_SIZ, "%d", i);
2599 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2602 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2603 for(i=1; i<100; i+=(i<10?1:5)) {
2604 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2605 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2606 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2608 snprintf(buf, MSG_SIZ, "%d", i);
2609 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2612 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2616 int SeekGraphClick(ClickType click, int x, int y, int moving)
2618 static int lastDown = 0, displayed = 0, lastSecond;
2619 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2620 if(click == Release || moving) return FALSE;
2622 soughtPending = TRUE;
2623 SendToICS(ics_prefix);
2624 SendToICS("sought\n"); // should this be "sought all"?
2625 } else { // issue challenge based on clicked ad
2626 int dist = 10000; int i, closest = 0, second = 0;
2627 for(i=0; i<nrOfSeekAds; i++) {
2628 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2629 if(d < dist) { dist = d; closest = i; }
2630 second += (d - zList[i] < 120); // count in-range ads
2631 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2635 second = (second > 1);
2636 if(displayed != closest || second != lastSecond) {
2637 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2638 lastSecond = second; displayed = closest;
2640 if(click == Press) {
2641 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2644 } // on press 'hit', only show info
2645 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2646 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2647 SendToICS(ics_prefix);
2649 return TRUE; // let incoming board of started game pop down the graph
2650 } else if(click == Release) { // release 'miss' is ignored
2651 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2652 if(moving == 2) { // right up-click
2653 nrOfSeekAds = 0; // refresh graph
2654 soughtPending = TRUE;
2655 SendToICS(ics_prefix);
2656 SendToICS("sought\n"); // should this be "sought all"?
2659 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2660 // press miss or release hit 'pop down' seek graph
2661 seekGraphUp = FALSE;
2662 DrawPosition(TRUE, NULL);
2668 read_from_ics(isr, closure, data, count, error)
2675 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2676 #define STARTED_NONE 0
2677 #define STARTED_MOVES 1
2678 #define STARTED_BOARD 2
2679 #define STARTED_OBSERVE 3
2680 #define STARTED_HOLDINGS 4
2681 #define STARTED_CHATTER 5
2682 #define STARTED_COMMENT 6
2683 #define STARTED_MOVES_NOHIDE 7
2685 static int started = STARTED_NONE;
2686 static char parse[20000];
2687 static int parse_pos = 0;
2688 static char buf[BUF_SIZE + 1];
2689 static int firstTime = TRUE, intfSet = FALSE;
2690 static ColorClass prevColor = ColorNormal;
2691 static int savingComment = FALSE;
2692 static int cmatch = 0; // continuation sequence match
2699 int backup; /* [DM] For zippy color lines */
2701 char talker[MSG_SIZ]; // [HGM] chat
2704 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2706 if (appData.debugMode) {
2708 fprintf(debugFP, "<ICS: ");
2709 show_bytes(debugFP, data, count);
2710 fprintf(debugFP, "\n");
2714 if (appData.debugMode) { int f = forwardMostMove;
2715 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2716 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2717 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2720 /* If last read ended with a partial line that we couldn't parse,
2721 prepend it to the new read and try again. */
2722 if (leftover_len > 0) {
2723 for (i=0; i<leftover_len; i++)
2724 buf[i] = buf[leftover_start + i];
2727 /* copy new characters into the buffer */
2728 bp = buf + leftover_len;
2729 buf_len=leftover_len;
2730 for (i=0; i<count; i++)
2733 if (data[i] == '\r')
2736 // join lines split by ICS?
2737 if (!appData.noJoin)
2740 Joining just consists of finding matches against the
2741 continuation sequence, and discarding that sequence
2742 if found instead of copying it. So, until a match
2743 fails, there's nothing to do since it might be the
2744 complete sequence, and thus, something we don't want
2747 if (data[i] == cont_seq[cmatch])
2750 if (cmatch == strlen(cont_seq))
2752 cmatch = 0; // complete match. just reset the counter
2755 it's possible for the ICS to not include the space
2756 at the end of the last word, making our [correct]
2757 join operation fuse two separate words. the server
2758 does this when the space occurs at the width setting.
2760 if (!buf_len || buf[buf_len-1] != ' ')
2771 match failed, so we have to copy what matched before
2772 falling through and copying this character. In reality,
2773 this will only ever be just the newline character, but
2774 it doesn't hurt to be precise.
2776 strncpy(bp, cont_seq, cmatch);
2788 buf[buf_len] = NULLCHAR;
2789 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2794 while (i < buf_len) {
2795 /* Deal with part of the TELNET option negotiation
2796 protocol. We refuse to do anything beyond the
2797 defaults, except that we allow the WILL ECHO option,
2798 which ICS uses to turn off password echoing when we are
2799 directly connected to it. We reject this option
2800 if localLineEditing mode is on (always on in xboard)
2801 and we are talking to port 23, which might be a real
2802 telnet server that will try to keep WILL ECHO on permanently.
2804 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2805 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2806 unsigned char option;
2808 switch ((unsigned char) buf[++i]) {
2810 if (appData.debugMode)
2811 fprintf(debugFP, "\n<WILL ");
2812 switch (option = (unsigned char) buf[++i]) {
2814 if (appData.debugMode)
2815 fprintf(debugFP, "ECHO ");
2816 /* Reply only if this is a change, according
2817 to the protocol rules. */
2818 if (remoteEchoOption) break;
2819 if (appData.localLineEditing &&
2820 atoi(appData.icsPort) == TN_PORT) {
2821 TelnetRequest(TN_DONT, TN_ECHO);
2824 TelnetRequest(TN_DO, TN_ECHO);
2825 remoteEchoOption = TRUE;
2829 if (appData.debugMode)
2830 fprintf(debugFP, "%d ", option);
2831 /* Whatever this is, we don't want it. */
2832 TelnetRequest(TN_DONT, option);
2837 if (appData.debugMode)
2838 fprintf(debugFP, "\n<WONT ");
2839 switch (option = (unsigned char) buf[++i]) {
2841 if (appData.debugMode)
2842 fprintf(debugFP, "ECHO ");
2843 /* Reply only if this is a change, according
2844 to the protocol rules. */
2845 if (!remoteEchoOption) break;
2847 TelnetRequest(TN_DONT, TN_ECHO);
2848 remoteEchoOption = FALSE;
2851 if (appData.debugMode)
2852 fprintf(debugFP, "%d ", (unsigned char) option);
2853 /* Whatever this is, it must already be turned
2854 off, because we never agree to turn on
2855 anything non-default, so according to the
2856 protocol rules, we don't reply. */
2861 if (appData.debugMode)
2862 fprintf(debugFP, "\n<DO ");
2863 switch (option = (unsigned char) buf[++i]) {
2865 /* Whatever this is, we refuse to do it. */
2866 if (appData.debugMode)
2867 fprintf(debugFP, "%d ", option);
2868 TelnetRequest(TN_WONT, option);
2873 if (appData.debugMode)
2874 fprintf(debugFP, "\n<DONT ");
2875 switch (option = (unsigned char) buf[++i]) {
2877 if (appData.debugMode)
2878 fprintf(debugFP, "%d ", option);
2879 /* Whatever this is, we are already not doing
2880 it, because we never agree to do anything
2881 non-default, so according to the protocol
2882 rules, we don't reply. */
2887 if (appData.debugMode)
2888 fprintf(debugFP, "\n<IAC ");
2889 /* Doubled IAC; pass it through */
2893 if (appData.debugMode)
2894 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2895 /* Drop all other telnet commands on the floor */
2898 if (oldi > next_out)
2899 SendToPlayer(&buf[next_out], oldi - next_out);
2905 /* OK, this at least will *usually* work */
2906 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2910 if (loggedOn && !intfSet) {
2911 if (ics_type == ICS_ICC) {
2912 snprintf(str, MSG_SIZ,
2913 "/set-quietly interface %s\n/set-quietly style 12\n",
2915 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2916 strcat(str, "/set-2 51 1\n/set seek 1\n");
2917 } else if (ics_type == ICS_CHESSNET) {
2918 snprintf(str, MSG_SIZ, "/style 12\n");
2920 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2921 strcat(str, programVersion);
2922 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2923 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2924 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2926 strcat(str, "$iset nohighlight 1\n");
2928 strcat(str, "$iset lock 1\n$style 12\n");
2931 NotifyFrontendLogin();
2935 if (started == STARTED_COMMENT) {
2936 /* Accumulate characters in comment */
2937 parse[parse_pos++] = buf[i];
2938 if (buf[i] == '\n') {
2939 parse[parse_pos] = NULLCHAR;
2940 if(chattingPartner>=0) {
2942 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2943 OutputChatMessage(chattingPartner, mess);
2944 chattingPartner = -1;
2945 next_out = i+1; // [HGM] suppress printing in ICS window
2947 if(!suppressKibitz) // [HGM] kibitz
2948 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2949 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2950 int nrDigit = 0, nrAlph = 0, j;
2951 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2952 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2953 parse[parse_pos] = NULLCHAR;
2954 // try to be smart: if it does not look like search info, it should go to
2955 // ICS interaction window after all, not to engine-output window.
2956 for(j=0; j<parse_pos; j++) { // count letters and digits
2957 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2958 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2959 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2961 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2962 int depth=0; float score;
2963 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2964 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2965 pvInfoList[forwardMostMove-1].depth = depth;
2966 pvInfoList[forwardMostMove-1].score = 100*score;
2968 OutputKibitz(suppressKibitz, parse);
2971 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2972 SendToPlayer(tmp, strlen(tmp));
2974 next_out = i+1; // [HGM] suppress printing in ICS window
2976 started = STARTED_NONE;
2978 /* Don't match patterns against characters in comment */
2983 if (started == STARTED_CHATTER) {
2984 if (buf[i] != '\n') {
2985 /* Don't match patterns against characters in chatter */
2989 started = STARTED_NONE;
2990 if(suppressKibitz) next_out = i+1;
2993 /* Kludge to deal with rcmd protocol */
2994 if (firstTime && looking_at(buf, &i, "\001*")) {
2995 DisplayFatalError(&buf[1], 0, 1);
3001 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3004 if (appData.debugMode)
3005 fprintf(debugFP, "ics_type %d\n", ics_type);
3008 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3009 ics_type = ICS_FICS;
3011 if (appData.debugMode)
3012 fprintf(debugFP, "ics_type %d\n", ics_type);
3015 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3016 ics_type = ICS_CHESSNET;
3018 if (appData.debugMode)
3019 fprintf(debugFP, "ics_type %d\n", ics_type);
3024 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3025 looking_at(buf, &i, "Logging you in as \"*\"") ||
3026 looking_at(buf, &i, "will be \"*\""))) {
3027 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3031 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3033 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3034 DisplayIcsInteractionTitle(buf);
3035 have_set_title = TRUE;
3038 /* skip finger notes */
3039 if (started == STARTED_NONE &&
3040 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3041 (buf[i] == '1' && buf[i+1] == '0')) &&
3042 buf[i+2] == ':' && buf[i+3] == ' ') {
3043 started = STARTED_CHATTER;
3049 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3050 if(appData.seekGraph) {
3051 if(soughtPending && MatchSoughtLine(buf+i)) {
3052 i = strstr(buf+i, "rated") - buf;
3053 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054 next_out = leftover_start = i;
3055 started = STARTED_CHATTER;
3056 suppressKibitz = TRUE;
3059 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3060 && looking_at(buf, &i, "* ads displayed")) {
3061 soughtPending = FALSE;
3066 if(appData.autoRefresh) {
3067 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3068 int s = (ics_type == ICS_ICC); // ICC format differs
3070 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3071 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3072 looking_at(buf, &i, "*% "); // eat prompt
3073 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3074 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075 next_out = i; // suppress
3078 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3079 char *p = star_match[0];
3081 if(seekGraphUp) RemoveSeekAd(atoi(p));
3082 while(*p && *p++ != ' '); // next
3084 looking_at(buf, &i, "*% "); // eat prompt
3085 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3092 /* skip formula vars */
3093 if (started == STARTED_NONE &&
3094 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3095 started = STARTED_CHATTER;
3100 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3101 if (appData.autoKibitz && started == STARTED_NONE &&
3102 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3103 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3104 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3105 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3106 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3107 suppressKibitz = TRUE;
3108 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3110 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3111 && (gameMode == IcsPlayingWhite)) ||
3112 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3113 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3114 started = STARTED_CHATTER; // own kibitz we simply discard
3116 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3117 parse_pos = 0; parse[0] = NULLCHAR;
3118 savingComment = TRUE;
3119 suppressKibitz = gameMode != IcsObserving ? 2 :
3120 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3124 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3125 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3126 && atoi(star_match[0])) {
3127 // suppress the acknowledgements of our own autoKibitz
3129 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3130 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3131 SendToPlayer(star_match[0], strlen(star_match[0]));
3132 if(looking_at(buf, &i, "*% ")) // eat prompt
3133 suppressKibitz = FALSE;
3137 } // [HGM] kibitz: end of patch
3139 // [HGM] chat: intercept tells by users for which we have an open chat window
3141 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3142 looking_at(buf, &i, "* whispers:") ||
3143 looking_at(buf, &i, "* kibitzes:") ||
3144 looking_at(buf, &i, "* shouts:") ||
3145 looking_at(buf, &i, "* c-shouts:") ||
3146 looking_at(buf, &i, "--> * ") ||
3147 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3148 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3149 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3150 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3152 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3153 chattingPartner = -1;
3155 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3156 for(p=0; p<MAX_CHAT; p++) {
3157 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3158 talker[0] = '['; strcat(talker, "] ");
3159 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3160 chattingPartner = p; break;
3163 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3164 for(p=0; p<MAX_CHAT; p++) {
3165 if(!strcmp("kibitzes", chatPartner[p])) {
3166 talker[0] = '['; strcat(talker, "] ");
3167 chattingPartner = p; break;
3170 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3171 for(p=0; p<MAX_CHAT; p++) {
3172 if(!strcmp("whispers", chatPartner[p])) {
3173 talker[0] = '['; strcat(talker, "] ");
3174 chattingPartner = p; break;
3177 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3178 if(buf[i-8] == '-' && buf[i-3] == 't')
3179 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3180 if(!strcmp("c-shouts", chatPartner[p])) {
3181 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3182 chattingPartner = p; break;
3185 if(chattingPartner < 0)
3186 for(p=0; p<MAX_CHAT; p++) {
3187 if(!strcmp("shouts", chatPartner[p])) {
3188 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3189 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3190 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3191 chattingPartner = p; break;
3195 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3196 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3197 talker[0] = 0; Colorize(ColorTell, FALSE);
3198 chattingPartner = p; break;
3200 if(chattingPartner<0) i = oldi; else {
3201 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3202 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3203 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3204 started = STARTED_COMMENT;
3205 parse_pos = 0; parse[0] = NULLCHAR;
3206 savingComment = 3 + chattingPartner; // counts as TRUE
3207 suppressKibitz = TRUE;
3210 } // [HGM] chat: end of patch
3213 if (appData.zippyTalk || appData.zippyPlay) {
3214 /* [DM] Backup address for color zippy lines */
3216 if (loggedOn == TRUE)
3217 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3218 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3220 } // [DM] 'else { ' deleted
3222 /* Regular tells and says */
3223 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3224 looking_at(buf, &i, "* (your partner) tells you: ") ||
3225 looking_at(buf, &i, "* says: ") ||
3226 /* Don't color "message" or "messages" output */
3227 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3228 looking_at(buf, &i, "*. * at *:*: ") ||
3229 looking_at(buf, &i, "--* (*:*): ") ||
3230 /* Message notifications (same color as tells) */
3231 looking_at(buf, &i, "* has left a message ") ||
3232 looking_at(buf, &i, "* just sent you a message:\n") ||
3233 /* Whispers and kibitzes */
3234 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3235 looking_at(buf, &i, "* kibitzes: ") ||
3237 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3239 if (tkind == 1 && strchr(star_match[0], ':')) {
3240 /* Avoid "tells you:" spoofs in channels */
3243 if (star_match[0][0] == NULLCHAR ||
3244 strchr(star_match[0], ' ') ||
3245 (tkind == 3 && strchr(star_match[1], ' '))) {
3246 /* Reject bogus matches */
3249 if (appData.colorize) {
3250 if (oldi > next_out) {
3251 SendToPlayer(&buf[next_out], oldi - next_out);
3256 Colorize(ColorTell, FALSE);
3257 curColor = ColorTell;
3260 Colorize(ColorKibitz, FALSE);
3261 curColor = ColorKibitz;
3264 p = strrchr(star_match[1], '(');
3271 Colorize(ColorChannel1, FALSE);
3272 curColor = ColorChannel1;
3274 Colorize(ColorChannel, FALSE);
3275 curColor = ColorChannel;
3279 curColor = ColorNormal;
3283 if (started == STARTED_NONE && appData.autoComment &&
3284 (gameMode == IcsObserving ||
3285 gameMode == IcsPlayingWhite ||
3286 gameMode == IcsPlayingBlack)) {
3287 parse_pos = i - oldi;
3288 memcpy(parse, &buf[oldi], parse_pos);
3289 parse[parse_pos] = NULLCHAR;
3290 started = STARTED_COMMENT;
3291 savingComment = TRUE;
3293 started = STARTED_CHATTER;
3294 savingComment = FALSE;
3301 if (looking_at(buf, &i, "* s-shouts: ") ||
3302 looking_at(buf, &i, "* c-shouts: ")) {
3303 if (appData.colorize) {
3304 if (oldi > next_out) {
3305 SendToPlayer(&buf[next_out], oldi - next_out);
3308 Colorize(ColorSShout, FALSE);
3309 curColor = ColorSShout;
3312 started = STARTED_CHATTER;
3316 if (looking_at(buf, &i, "--->")) {
3321 if (looking_at(buf, &i, "* shouts: ") ||
3322 looking_at(buf, &i, "--> ")) {
3323 if (appData.colorize) {
3324 if (oldi > next_out) {
3325 SendToPlayer(&buf[next_out], oldi - next_out);
3328 Colorize(ColorShout, FALSE);
3329 curColor = ColorShout;
3332 started = STARTED_CHATTER;
3336 if (looking_at( buf, &i, "Challenge:")) {
3337 if (appData.colorize) {
3338 if (oldi > next_out) {
3339 SendToPlayer(&buf[next_out], oldi - next_out);
3342 Colorize(ColorChallenge, FALSE);
3343 curColor = ColorChallenge;
3349 if (looking_at(buf, &i, "* offers you") ||
3350 looking_at(buf, &i, "* offers to be") ||
3351 looking_at(buf, &i, "* would like to") ||
3352 looking_at(buf, &i, "* requests to") ||
3353 looking_at(buf, &i, "Your opponent offers") ||
3354 looking_at(buf, &i, "Your opponent requests")) {
3356 if (appData.colorize) {
3357 if (oldi > next_out) {
3358 SendToPlayer(&buf[next_out], oldi - next_out);
3361 Colorize(ColorRequest, FALSE);
3362 curColor = ColorRequest;
3367 if (looking_at(buf, &i, "* (*) seeking")) {
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(ColorSeek, FALSE);
3374 curColor = ColorSeek;
3379 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3381 if (looking_at(buf, &i, "\\ ")) {
3382 if (prevColor != ColorNormal) {
3383 if (oldi > next_out) {
3384 SendToPlayer(&buf[next_out], oldi - next_out);
3387 Colorize(prevColor, TRUE);
3388 curColor = prevColor;
3390 if (savingComment) {
3391 parse_pos = i - oldi;
3392 memcpy(parse, &buf[oldi], parse_pos);
3393 parse[parse_pos] = NULLCHAR;
3394 started = STARTED_COMMENT;
3395 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3396 chattingPartner = savingComment - 3; // kludge to remember the box
3398 started = STARTED_CHATTER;
3403 if (looking_at(buf, &i, "Black Strength :") ||
3404 looking_at(buf, &i, "<<< style 10 board >>>") ||
3405 looking_at(buf, &i, "<10>") ||
3406 looking_at(buf, &i, "#@#")) {
3407 /* Wrong board style */
3409 SendToICS(ics_prefix);
3410 SendToICS("set style 12\n");
3411 SendToICS(ics_prefix);
3412 SendToICS("refresh\n");
3416 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3418 have_sent_ICS_logon = 1;
3422 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3423 (looking_at(buf, &i, "\n<12> ") ||
3424 looking_at(buf, &i, "<12> "))) {
3426 if (oldi > next_out) {
3427 SendToPlayer(&buf[next_out], oldi - next_out);
3430 started = STARTED_BOARD;
3435 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3436 looking_at(buf, &i, "<b1> ")) {
3437 if (oldi > next_out) {
3438 SendToPlayer(&buf[next_out], oldi - next_out);
3441 started = STARTED_HOLDINGS;
3446 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3448 /* Header for a move list -- first line */
3450 switch (ics_getting_history) {
3454 case BeginningOfGame:
3455 /* User typed "moves" or "oldmoves" while we
3456 were idle. Pretend we asked for these
3457 moves and soak them up so user can step
3458 through them and/or save them.
3461 gameMode = IcsObserving;
3464 ics_getting_history = H_GOT_UNREQ_HEADER;
3466 case EditGame: /*?*/
3467 case EditPosition: /*?*/
3468 /* Should above feature work in these modes too? */
3469 /* For now it doesn't */
3470 ics_getting_history = H_GOT_UNWANTED_HEADER;
3473 ics_getting_history = H_GOT_UNWANTED_HEADER;
3478 /* Is this the right one? */
3479 if (gameInfo.white && gameInfo.black &&
3480 strcmp(gameInfo.white, star_match[0]) == 0 &&
3481 strcmp(gameInfo.black, star_match[2]) == 0) {
3483 ics_getting_history = H_GOT_REQ_HEADER;
3486 case H_GOT_REQ_HEADER:
3487 case H_GOT_UNREQ_HEADER:
3488 case H_GOT_UNWANTED_HEADER:
3489 case H_GETTING_MOVES:
3490 /* Should not happen */
3491 DisplayError(_("Error gathering move list: two headers"), 0);
3492 ics_getting_history = H_FALSE;
3496 /* Save player ratings into gameInfo if needed */
3497 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3498 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3499 (gameInfo.whiteRating == -1 ||
3500 gameInfo.blackRating == -1)) {
3502 gameInfo.whiteRating = string_to_rating(star_match[1]);
3503 gameInfo.blackRating = string_to_rating(star_match[3]);
3504 if (appData.debugMode)
3505 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3506 gameInfo.whiteRating, gameInfo.blackRating);
3511 if (looking_at(buf, &i,
3512 "* * match, initial time: * minute*, increment: * second")) {
3513 /* Header for a move list -- second line */
3514 /* Initial board will follow if this is a wild game */
3515 if (gameInfo.event != NULL) free(gameInfo.event);
3516 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3517 gameInfo.event = StrSave(str);
3518 /* [HGM] we switched variant. Translate boards if needed. */
3519 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3523 if (looking_at(buf, &i, "Move ")) {
3524 /* Beginning of a move list */
3525 switch (ics_getting_history) {
3527 /* Normally should not happen */
3528 /* Maybe user hit reset while we were parsing */
3531 /* Happens if we are ignoring a move list that is not
3532 * the one we just requested. Common if the user
3533 * tries to observe two games without turning off
3536 case H_GETTING_MOVES:
3537 /* Should not happen */
3538 DisplayError(_("Error gathering move list: nested"), 0);
3539 ics_getting_history = H_FALSE;
3541 case H_GOT_REQ_HEADER:
3542 ics_getting_history = H_GETTING_MOVES;
3543 started = STARTED_MOVES;
3545 if (oldi > next_out) {
3546 SendToPlayer(&buf[next_out], oldi - next_out);
3549 case H_GOT_UNREQ_HEADER:
3550 ics_getting_history = H_GETTING_MOVES;
3551 started = STARTED_MOVES_NOHIDE;
3554 case H_GOT_UNWANTED_HEADER:
3555 ics_getting_history = H_FALSE;
3561 if (looking_at(buf, &i, "% ") ||
3562 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3563 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3564 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3565 soughtPending = FALSE;
3569 if(suppressKibitz) next_out = i;
3570 savingComment = FALSE;
3574 case STARTED_MOVES_NOHIDE:
3575 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3576 parse[parse_pos + i - oldi] = NULLCHAR;
3577 ParseGameHistory(parse);
3579 if (appData.zippyPlay && first.initDone) {
3580 FeedMovesToProgram(&first, forwardMostMove);
3581 if (gameMode == IcsPlayingWhite) {
3582 if (WhiteOnMove(forwardMostMove)) {
3583 if (first.sendTime) {
3584 if (first.useColors) {
3585 SendToProgram("black\n", &first);
3587 SendTimeRemaining(&first, TRUE);
3589 if (first.useColors) {
3590 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3592 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3593 first.maybeThinking = TRUE;
3595 if (first.usePlayother) {
3596 if (first.sendTime) {
3597 SendTimeRemaining(&first, TRUE);
3599 SendToProgram("playother\n", &first);
3605 } else if (gameMode == IcsPlayingBlack) {
3606 if (!WhiteOnMove(forwardMostMove)) {
3607 if (first.sendTime) {
3608 if (first.useColors) {
3609 SendToProgram("white\n", &first);
3611 SendTimeRemaining(&first, FALSE);
3613 if (first.useColors) {
3614 SendToProgram("black\n", &first);
3616 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3617 first.maybeThinking = TRUE;
3619 if (first.usePlayother) {
3620 if (first.sendTime) {
3621 SendTimeRemaining(&first, FALSE);
3623 SendToProgram("playother\n", &first);
3632 if (gameMode == IcsObserving && ics_gamenum == -1) {
3633 /* Moves came from oldmoves or moves command
3634 while we weren't doing anything else.
3636 currentMove = forwardMostMove;
3637 ClearHighlights();/*!!could figure this out*/
3638 flipView = appData.flipView;
3639 DrawPosition(TRUE, boards[currentMove]);
3640 DisplayBothClocks();
3641 snprintf(str, MSG_SIZ, "%s vs. %s",
3642 gameInfo.white, gameInfo.black);
3646 /* Moves were history of an active game */
3647 if (gameInfo.resultDetails != NULL) {
3648 free(gameInfo.resultDetails);
3649 gameInfo.resultDetails = NULL;
3652 HistorySet(parseList, backwardMostMove,
3653 forwardMostMove, currentMove-1);
3654 DisplayMove(currentMove - 1);
3655 if (started == STARTED_MOVES) next_out = i;
3656 started = STARTED_NONE;
3657 ics_getting_history = H_FALSE;
3660 case STARTED_OBSERVE:
3661 started = STARTED_NONE;
3662 SendToICS(ics_prefix);
3663 SendToICS("refresh\n");
3669 if(bookHit) { // [HGM] book: simulate book reply
3670 static char bookMove[MSG_SIZ]; // a bit generous?
3672 programStats.nodes = programStats.depth = programStats.time =
3673 programStats.score = programStats.got_only_move = 0;
3674 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3676 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3677 strcat(bookMove, bookHit);
3678 HandleMachineMove(bookMove, &first);
3683 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3684 started == STARTED_HOLDINGS ||
3685 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3686 /* Accumulate characters in move list or board */
3687 parse[parse_pos++] = buf[i];
3690 /* Start of game messages. Mostly we detect start of game
3691 when the first board image arrives. On some versions
3692 of the ICS, though, we need to do a "refresh" after starting
3693 to observe in order to get the current board right away. */
3694 if (looking_at(buf, &i, "Adding game * to observation list")) {
3695 started = STARTED_OBSERVE;
3699 /* Handle auto-observe */
3700 if (appData.autoObserve &&
3701 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3702 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3704 /* Choose the player that was highlighted, if any. */
3705 if (star_match[0][0] == '\033' ||
3706 star_match[1][0] != '\033') {
3707 player = star_match[0];
3709 player = star_match[2];
3711 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3712 ics_prefix, StripHighlightAndTitle(player));
3715 /* Save ratings from notify string */
3716 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3717 player1Rating = string_to_rating(star_match[1]);
3718 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3719 player2Rating = string_to_rating(star_match[3]);
3721 if (appData.debugMode)
3723 "Ratings from 'Game notification:' %s %d, %s %d\n",
3724 player1Name, player1Rating,
3725 player2Name, player2Rating);
3730 /* Deal with automatic examine mode after a game,
3731 and with IcsObserving -> IcsExamining transition */
3732 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3733 looking_at(buf, &i, "has made you an examiner of game *")) {
3735 int gamenum = atoi(star_match[0]);
3736 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3737 gamenum == ics_gamenum) {
3738 /* We were already playing or observing this game;
3739 no need to refetch history */
3740 gameMode = IcsExamining;
3742 pauseExamForwardMostMove = forwardMostMove;
3743 } else if (currentMove < forwardMostMove) {
3744 ForwardInner(forwardMostMove);
3747 /* I don't think this case really can happen */
3748 SendToICS(ics_prefix);
3749 SendToICS("refresh\n");
3754 /* Error messages */
3755 // if (ics_user_moved) {
3756 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3757 if (looking_at(buf, &i, "Illegal move") ||
3758 looking_at(buf, &i, "Not a legal move") ||
3759 looking_at(buf, &i, "Your king is in check") ||
3760 looking_at(buf, &i, "It isn't your turn") ||
3761 looking_at(buf, &i, "It is not your move")) {
3763 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3764 currentMove = forwardMostMove-1;
3765 DisplayMove(currentMove - 1); /* before DMError */
3766 DrawPosition(FALSE, boards[currentMove]);
3767 SwitchClocks(forwardMostMove-1); // [HGM] race
3768 DisplayBothClocks();
3770 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3776 if (looking_at(buf, &i, "still have time") ||
3777 looking_at(buf, &i, "not out of time") ||
3778 looking_at(buf, &i, "either player is out of time") ||
3779 looking_at(buf, &i, "has timeseal; checking")) {
3780 /* We must have called his flag a little too soon */
3781 whiteFlag = blackFlag = FALSE;
3785 if (looking_at(buf, &i, "added * seconds to") ||
3786 looking_at(buf, &i, "seconds were added to")) {
3787 /* Update the clocks */
3788 SendToICS(ics_prefix);
3789 SendToICS("refresh\n");
3793 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3794 ics_clock_paused = TRUE;
3799 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3800 ics_clock_paused = FALSE;
3805 /* Grab player ratings from the Creating: message.
3806 Note we have to check for the special case when
3807 the ICS inserts things like [white] or [black]. */
3808 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3809 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3811 0 player 1 name (not necessarily white)
3813 2 empty, white, or black (IGNORED)
3814 3 player 2 name (not necessarily black)
3817 The names/ratings are sorted out when the game
3818 actually starts (below).
3820 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3821 player1Rating = string_to_rating(star_match[1]);
3822 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3823 player2Rating = string_to_rating(star_match[4]);
3825 if (appData.debugMode)
3827 "Ratings from 'Creating:' %s %d, %s %d\n",
3828 player1Name, player1Rating,
3829 player2Name, player2Rating);
3834 /* Improved generic start/end-of-game messages */
3835 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3836 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3837 /* If tkind == 0: */
3838 /* star_match[0] is the game number */
3839 /* [1] is the white player's name */
3840 /* [2] is the black player's name */
3841 /* For end-of-game: */
3842 /* [3] is the reason for the game end */
3843 /* [4] is a PGN end game-token, preceded by " " */
3844 /* For start-of-game: */
3845 /* [3] begins with "Creating" or "Continuing" */
3846 /* [4] is " *" or empty (don't care). */
3847 int gamenum = atoi(star_match[0]);
3848 char *whitename, *blackname, *why, *endtoken;
3849 ChessMove endtype = EndOfFile;
3852 whitename = star_match[1];
3853 blackname = star_match[2];
3854 why = star_match[3];
3855 endtoken = star_match[4];
3857 whitename = star_match[1];
3858 blackname = star_match[3];
3859 why = star_match[5];
3860 endtoken = star_match[6];
3863 /* Game start messages */
3864 if (strncmp(why, "Creating ", 9) == 0 ||
3865 strncmp(why, "Continuing ", 11) == 0) {
3866 gs_gamenum = gamenum;
3867 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3868 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3870 if (appData.zippyPlay) {
3871 ZippyGameStart(whitename, blackname);
3874 partnerBoardValid = FALSE; // [HGM] bughouse
3878 /* Game end messages */
3879 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3880 ics_gamenum != gamenum) {
3883 while (endtoken[0] == ' ') endtoken++;
3884 switch (endtoken[0]) {
3887 endtype = GameUnfinished;
3890 endtype = BlackWins;
3893 if (endtoken[1] == '/')
3894 endtype = GameIsDrawn;
3896 endtype = WhiteWins;
3899 GameEnds(endtype, why, GE_ICS);
3901 if (appData.zippyPlay && first.initDone) {
3902 ZippyGameEnd(endtype, why);
3903 if (first.pr == NoProc) {
3904 /* Start the next process early so that we'll
3905 be ready for the next challenge */
3906 StartChessProgram(&first);
3908 /* Send "new" early, in case this command takes
3909 a long time to finish, so that we'll be ready
3910 for the next challenge. */
3911 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3915 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3919 if (looking_at(buf, &i, "Removing game * from observation") ||
3920 looking_at(buf, &i, "no longer observing game *") ||
3921 looking_at(buf, &i, "Game * (*) has no examiners")) {
3922 if (gameMode == IcsObserving &&
3923 atoi(star_match[0]) == ics_gamenum)
3925 /* icsEngineAnalyze */
3926 if (appData.icsEngineAnalyze) {
3933 ics_user_moved = FALSE;
3938 if (looking_at(buf, &i, "no longer examining game *")) {
3939 if (gameMode == IcsExamining &&
3940 atoi(star_match[0]) == ics_gamenum)
3944 ics_user_moved = FALSE;
3949 /* Advance leftover_start past any newlines we find,
3950 so only partial lines can get reparsed */
3951 if (looking_at(buf, &i, "\n")) {
3952 prevColor = curColor;
3953 if (curColor != ColorNormal) {
3954 if (oldi > next_out) {
3955 SendToPlayer(&buf[next_out], oldi - next_out);
3958 Colorize(ColorNormal, FALSE);
3959 curColor = ColorNormal;
3961 if (started == STARTED_BOARD) {
3962 started = STARTED_NONE;
3963 parse[parse_pos] = NULLCHAR;
3964 ParseBoard12(parse);
3967 /* Send premove here */
3968 if (appData.premove) {
3970 if (currentMove == 0 &&
3971 gameMode == IcsPlayingWhite &&
3972 appData.premoveWhite) {
3973 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3974 if (appData.debugMode)
3975 fprintf(debugFP, "Sending premove:\n");
3977 } else if (currentMove == 1 &&
3978 gameMode == IcsPlayingBlack &&
3979 appData.premoveBlack) {
3980 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3981 if (appData.debugMode)
3982 fprintf(debugFP, "Sending premove:\n");
3984 } else if (gotPremove) {
3986 ClearPremoveHighlights();
3987 if (appData.debugMode)
3988 fprintf(debugFP, "Sending premove:\n");
3989 UserMoveEvent(premoveFromX, premoveFromY,
3990 premoveToX, premoveToY,
3995 /* Usually suppress following prompt */
3996 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3997 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3998 if (looking_at(buf, &i, "*% ")) {
3999 savingComment = FALSE;
4004 } else if (started == STARTED_HOLDINGS) {
4006 char new_piece[MSG_SIZ];
4007 started = STARTED_NONE;
4008 parse[parse_pos] = NULLCHAR;
4009 if (appData.debugMode)
4010 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4011 parse, currentMove);
4012 if (sscanf(parse, " game %d", &gamenum) == 1) {
4013 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4014 if (gameInfo.variant == VariantNormal) {
4015 /* [HGM] We seem to switch variant during a game!
4016 * Presumably no holdings were displayed, so we have
4017 * to move the position two files to the right to
4018 * create room for them!
4020 VariantClass newVariant;
4021 switch(gameInfo.boardWidth) { // base guess on board width
4022 case 9: newVariant = VariantShogi; break;
4023 case 10: newVariant = VariantGreat; break;
4024 default: newVariant = VariantCrazyhouse; break;
4026 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4027 /* Get a move list just to see the header, which
4028 will tell us whether this is really bug or zh */
4029 if (ics_getting_history == H_FALSE) {
4030 ics_getting_history = H_REQUESTED;
4031 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4035 new_piece[0] = NULLCHAR;
4036 sscanf(parse, "game %d white [%s black [%s <- %s",
4037 &gamenum, white_holding, black_holding,
4039 white_holding[strlen(white_holding)-1] = NULLCHAR;
4040 black_holding[strlen(black_holding)-1] = NULLCHAR;
4041 /* [HGM] copy holdings to board holdings area */
4042 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4043 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4044 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4046 if (appData.zippyPlay && first.initDone) {
4047 ZippyHoldings(white_holding, black_holding,
4051 if (tinyLayout || smallLayout) {
4052 char wh[16], bh[16];
4053 PackHolding(wh, white_holding);
4054 PackHolding(bh, black_holding);
4055 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4056 gameInfo.white, gameInfo.black);
4058 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4059 gameInfo.white, white_holding,
4060 gameInfo.black, black_holding);
4062 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4063 DrawPosition(FALSE, boards[currentMove]);
4065 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4066 sscanf(parse, "game %d white [%s black [%s <- %s",
4067 &gamenum, white_holding, black_holding,
4069 white_holding[strlen(white_holding)-1] = NULLCHAR;
4070 black_holding[strlen(black_holding)-1] = NULLCHAR;
4071 /* [HGM] copy holdings to partner-board holdings area */
4072 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4073 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4074 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4075 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4076 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4079 /* Suppress following prompt */
4080 if (looking_at(buf, &i, "*% ")) {
4081 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4082 savingComment = FALSE;
4090 i++; /* skip unparsed character and loop back */
4093 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4094 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4095 // SendToPlayer(&buf[next_out], i - next_out);
4096 started != STARTED_HOLDINGS && leftover_start > next_out) {
4097 SendToPlayer(&buf[next_out], leftover_start - next_out);
4101 leftover_len = buf_len - leftover_start;
4102 /* if buffer ends with something we couldn't parse,
4103 reparse it after appending the next read */
4105 } else if (count == 0) {
4106 RemoveInputSource(isr);
4107 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4109 DisplayFatalError(_("Error reading from ICS"), error, 1);
4114 /* Board style 12 looks like this:
4116 <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
4118 * The "<12> " is stripped before it gets to this routine. The two
4119 * trailing 0's (flip state and clock ticking) are later addition, and
4120 * some chess servers may not have them, or may have only the first.
4121 * Additional trailing fields may be added in the future.
4124 #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"
4126 #define RELATION_OBSERVING_PLAYED 0
4127 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4128 #define RELATION_PLAYING_MYMOVE 1
4129 #define RELATION_PLAYING_NOTMYMOVE -1
4130 #define RELATION_EXAMINING 2
4131 #define RELATION_ISOLATED_BOARD -3
4132 #define RELATION_STARTING_POSITION -4 /* FICS only */
4135 ParseBoard12(string)
4138 GameMode newGameMode;
4139 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4140 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4141 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4142 char to_play, board_chars[200];
4143 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4144 char black[32], white[32];
4146 int prevMove = currentMove;
4149 int fromX, fromY, toX, toY;
4151 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4152 char *bookHit = NULL; // [HGM] book
4153 Boolean weird = FALSE, reqFlag = FALSE;
4155 fromX = fromY = toX = toY = -1;
4159 if (appData.debugMode)
4160 fprintf(debugFP, _("Parsing board: %s\n"), string);
4162 move_str[0] = NULLCHAR;
4163 elapsed_time[0] = NULLCHAR;
4164 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4166 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4167 if(string[i] == ' ') { ranks++; files = 0; }
4169 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4172 for(j = 0; j <i; j++) board_chars[j] = string[j];
4173 board_chars[i] = '\0';
4176 n = sscanf(string, PATTERN, &to_play, &double_push,
4177 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4178 &gamenum, white, black, &relation, &basetime, &increment,
4179 &white_stren, &black_stren, &white_time, &black_time,
4180 &moveNum, str, elapsed_time, move_str, &ics_flip,
4184 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4185 DisplayError(str, 0);
4189 /* Convert the move number to internal form */
4190 moveNum = (moveNum - 1) * 2;
4191 if (to_play == 'B') moveNum++;
4192 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4193 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4199 case RELATION_OBSERVING_PLAYED:
4200 case RELATION_OBSERVING_STATIC:
4201 if (gamenum == -1) {
4202 /* Old ICC buglet */
4203 relation = RELATION_OBSERVING_STATIC;
4205 newGameMode = IcsObserving;
4207 case RELATION_PLAYING_MYMOVE:
4208 case RELATION_PLAYING_NOTMYMOVE:
4210 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4211 IcsPlayingWhite : IcsPlayingBlack;
4213 case RELATION_EXAMINING:
4214 newGameMode = IcsExamining;
4216 case RELATION_ISOLATED_BOARD:
4218 /* Just display this board. If user was doing something else,
4219 we will forget about it until the next board comes. */
4220 newGameMode = IcsIdle;
4222 case RELATION_STARTING_POSITION:
4223 newGameMode = gameMode;
4227 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4228 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4229 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4231 for (k = 0; k < ranks; k++) {
4232 for (j = 0; j < files; j++)
4233 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4234 if(gameInfo.holdingsWidth > 1) {
4235 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4236 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4239 CopyBoard(partnerBoard, board);
4240 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4241 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4242 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4243 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4244 if(toSqr = strchr(str, '-')) {
4245 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4246 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4247 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4248 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4249 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4250 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4251 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4252 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4253 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4254 DisplayMessage(partnerStatus, "");
4255 partnerBoardValid = TRUE;
4259 /* Modify behavior for initial board display on move listing
4262 switch (ics_getting_history) {
4266 case H_GOT_REQ_HEADER:
4267 case H_GOT_UNREQ_HEADER:
4268 /* This is the initial position of the current game */
4269 gamenum = ics_gamenum;
4270 moveNum = 0; /* old ICS bug workaround */
4271 if (to_play == 'B') {
4272 startedFromSetupPosition = TRUE;
4273 blackPlaysFirst = TRUE;
4275 if (forwardMostMove == 0) forwardMostMove = 1;
4276 if (backwardMostMove == 0) backwardMostMove = 1;
4277 if (currentMove == 0) currentMove = 1;
4279 newGameMode = gameMode;
4280 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4282 case H_GOT_UNWANTED_HEADER:
4283 /* This is an initial board that we don't want */
4285 case H_GETTING_MOVES:
4286 /* Should not happen */
4287 DisplayError(_("Error gathering move list: extra board"), 0);
4288 ics_getting_history = H_FALSE;
4292 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4293 weird && (int)gameInfo.variant < (int)VariantShogi) {
4294 /* [HGM] We seem to have switched variant unexpectedly
4295 * Try to guess new variant from board size
4297 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4298 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4299 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4300 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4301 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4302 if(!weird) newVariant = VariantNormal;
4303 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4304 /* Get a move list just to see the header, which
4305 will tell us whether this is really bug or zh */
4306 if (ics_getting_history == H_FALSE) {
4307 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4308 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4313 /* Take action if this is the first board of a new game, or of a
4314 different game than is currently being displayed. */
4315 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4316 relation == RELATION_ISOLATED_BOARD) {
4318 /* Forget the old game and get the history (if any) of the new one */
4319 if (gameMode != BeginningOfGame) {
4323 if (appData.autoRaiseBoard) BoardToTop();
4325 if (gamenum == -1) {
4326 newGameMode = IcsIdle;
4327 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4328 appData.getMoveList && !reqFlag) {
4329 /* Need to get game history */
4330 ics_getting_history = H_REQUESTED;
4331 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4335 /* Initially flip the board to have black on the bottom if playing
4336 black or if the ICS flip flag is set, but let the user change
4337 it with the Flip View button. */
4338 flipView = appData.autoFlipView ?
4339 (newGameMode == IcsPlayingBlack) || ics_flip :
4342 /* Done with values from previous mode; copy in new ones */
4343 gameMode = newGameMode;
4345 ics_gamenum = gamenum;
4346 if (gamenum == gs_gamenum) {
4347 int klen = strlen(gs_kind);
4348 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4349 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4350 gameInfo.event = StrSave(str);
4352 gameInfo.event = StrSave("ICS game");
4354 gameInfo.site = StrSave(appData.icsHost);
4355 gameInfo.date = PGNDate();
4356 gameInfo.round = StrSave("-");
4357 gameInfo.white = StrSave(white);
4358 gameInfo.black = StrSave(black);
4359 timeControl = basetime * 60 * 1000;
4361 timeIncrement = increment * 1000;
4362 movesPerSession = 0;
4363 gameInfo.timeControl = TimeControlTagValue();
4364 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4365 if (appData.debugMode) {
4366 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4367 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4368 setbuf(debugFP, NULL);
4371 gameInfo.outOfBook = NULL;
4373 /* Do we have the ratings? */
4374 if (strcmp(player1Name, white) == 0 &&
4375 strcmp(player2Name, black) == 0) {
4376 if (appData.debugMode)
4377 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4378 player1Rating, player2Rating);
4379 gameInfo.whiteRating = player1Rating;
4380 gameInfo.blackRating = player2Rating;
4381 } else if (strcmp(player2Name, white) == 0 &&
4382 strcmp(player1Name, black) == 0) {
4383 if (appData.debugMode)
4384 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4385 player2Rating, player1Rating);
4386 gameInfo.whiteRating = player2Rating;
4387 gameInfo.blackRating = player1Rating;
4389 player1Name[0] = player2Name[0] = NULLCHAR;
4391 /* Silence shouts if requested */
4392 if (appData.quietPlay &&
4393 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4394 SendToICS(ics_prefix);
4395 SendToICS("set shout 0\n");
4399 /* Deal with midgame name changes */
4401 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4402 if (gameInfo.white) free(gameInfo.white);
4403 gameInfo.white = StrSave(white);
4405 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4406 if (gameInfo.black) free(gameInfo.black);
4407 gameInfo.black = StrSave(black);
4411 /* Throw away game result if anything actually changes in examine mode */
4412 if (gameMode == IcsExamining && !newGame) {
4413 gameInfo.result = GameUnfinished;
4414 if (gameInfo.resultDetails != NULL) {
4415 free(gameInfo.resultDetails);
4416 gameInfo.resultDetails = NULL;
4420 /* In pausing && IcsExamining mode, we ignore boards coming
4421 in if they are in a different variation than we are. */
4422 if (pauseExamInvalid) return;
4423 if (pausing && gameMode == IcsExamining) {
4424 if (moveNum <= pauseExamForwardMostMove) {
4425 pauseExamInvalid = TRUE;
4426 forwardMostMove = pauseExamForwardMostMove;
4431 if (appData.debugMode) {
4432 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4434 /* Parse the board */
4435 for (k = 0; k < ranks; k++) {
4436 for (j = 0; j < files; j++)
4437 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4438 if(gameInfo.holdingsWidth > 1) {
4439 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4440 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4443 CopyBoard(boards[moveNum], board);
4444 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4446 startedFromSetupPosition =
4447 !CompareBoards(board, initialPosition);
4448 if(startedFromSetupPosition)
4449 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4452 /* [HGM] Set castling rights. Take the outermost Rooks,
4453 to make it also work for FRC opening positions. Note that board12
4454 is really defective for later FRC positions, as it has no way to
4455 indicate which Rook can castle if they are on the same side of King.
4456 For the initial position we grant rights to the outermost Rooks,
4457 and remember thos rights, and we then copy them on positions
4458 later in an FRC game. This means WB might not recognize castlings with
4459 Rooks that have moved back to their original position as illegal,
4460 but in ICS mode that is not its job anyway.
4462 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4463 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4465 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4466 if(board[0][i] == WhiteRook) j = i;
4467 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4469 if(board[0][i] == WhiteRook) j = i;
4470 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4471 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4473 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4476 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4478 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4479 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4480 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4481 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4482 if(board[BOARD_HEIGHT-1][k] == bKing)
4483 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4484 if(gameInfo.variant == VariantTwoKings) {
4485 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4486 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4487 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4490 r = boards[moveNum][CASTLING][0] = initialRights[0];
4491 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4492 r = boards[moveNum][CASTLING][1] = initialRights[1];
4493 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4494 r = boards[moveNum][CASTLING][3] = initialRights[3];
4495 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4496 r = boards[moveNum][CASTLING][4] = initialRights[4];
4497 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4498 /* wildcastle kludge: always assume King has rights */
4499 r = boards[moveNum][CASTLING][2] = initialRights[2];
4500 r = boards[moveNum][CASTLING][5] = initialRights[5];
4502 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4503 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4506 if (ics_getting_history == H_GOT_REQ_HEADER ||
4507 ics_getting_history == H_GOT_UNREQ_HEADER) {
4508 /* This was an initial position from a move list, not
4509 the current position */
4513 /* Update currentMove and known move number limits */
4514 newMove = newGame || moveNum > forwardMostMove;
4517 forwardMostMove = backwardMostMove = currentMove = moveNum;
4518 if (gameMode == IcsExamining && moveNum == 0) {
4519 /* Workaround for ICS limitation: we are not told the wild
4520 type when starting to examine a game. But if we ask for
4521 the move list, the move list header will tell us */
4522 ics_getting_history = H_REQUESTED;
4523 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4526 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4527 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4529 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4530 /* [HGM] applied this also to an engine that is silently watching */
4531 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4532 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4533 gameInfo.variant == currentlyInitializedVariant) {
4534 takeback = forwardMostMove - moveNum;
4535 for (i = 0; i < takeback; i++) {
4536 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4537 SendToProgram("undo\n", &first);
4542 forwardMostMove = moveNum;
4543 if (!pausing || currentMove > forwardMostMove)
4544 currentMove = forwardMostMove;
4546 /* New part of history that is not contiguous with old part */
4547 if (pausing && gameMode == IcsExamining) {
4548 pauseExamInvalid = TRUE;
4549 forwardMostMove = pauseExamForwardMostMove;
4552 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4554 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4555 // [HGM] when we will receive the move list we now request, it will be
4556 // fed to the engine from the first move on. So if the engine is not
4557 // in the initial position now, bring it there.
4558 InitChessProgram(&first, 0);
4561 ics_getting_history = H_REQUESTED;
4562 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4565 forwardMostMove = backwardMostMove = currentMove = moveNum;
4568 /* Update the clocks */
4569 if (strchr(elapsed_time, '.')) {
4571 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4572 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4574 /* Time is in seconds */
4575 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4576 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4581 if (appData.zippyPlay && newGame &&
4582 gameMode != IcsObserving && gameMode != IcsIdle &&
4583 gameMode != IcsExamining)
4584 ZippyFirstBoard(moveNum, basetime, increment);
4587 /* Put the move on the move list, first converting
4588 to canonical algebraic form. */
4590 if (appData.debugMode) {
4591 if (appData.debugMode) { int f = forwardMostMove;
4592 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4593 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4594 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4596 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4597 fprintf(debugFP, "moveNum = %d\n", moveNum);
4598 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4599 setbuf(debugFP, NULL);
4601 if (moveNum <= backwardMostMove) {
4602 /* We don't know what the board looked like before
4604 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4605 strcat(parseList[moveNum - 1], " ");
4606 strcat(parseList[moveNum - 1], elapsed_time);
4607 moveList[moveNum - 1][0] = NULLCHAR;
4608 } else if (strcmp(move_str, "none") == 0) {
4609 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4610 /* Again, we don't know what the board looked like;
4611 this is really the start of the game. */
4612 parseList[moveNum - 1][0] = NULLCHAR;
4613 moveList[moveNum - 1][0] = NULLCHAR;
4614 backwardMostMove = moveNum;
4615 startedFromSetupPosition = TRUE;
4616 fromX = fromY = toX = toY = -1;
4618 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4619 // So we parse the long-algebraic move string in stead of the SAN move
4620 int valid; char buf[MSG_SIZ], *prom;
4622 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4623 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4624 // str looks something like "Q/a1-a2"; kill the slash
4626 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4627 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4628 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4629 strcat(buf, prom); // long move lacks promo specification!
4630 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4631 if(appData.debugMode)
4632 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4633 safeStrCpy(move_str, buf, MSG_SIZ);
4635 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4636 &fromX, &fromY, &toX, &toY, &promoChar)
4637 || ParseOneMove(buf, moveNum - 1, &moveType,
4638 &fromX, &fromY, &toX, &toY, &promoChar);
4639 // end of long SAN patch
4641 (void) CoordsToAlgebraic(boards[moveNum - 1],
4642 PosFlags(moveNum - 1),
4643 fromY, fromX, toY, toX, promoChar,
4644 parseList[moveNum-1]);
4645 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4651 if(gameInfo.variant != VariantShogi)
4652 strcat(parseList[moveNum - 1], "+");
4655 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4656 strcat(parseList[moveNum - 1], "#");
4659 strcat(parseList[moveNum - 1], " ");
4660 strcat(parseList[moveNum - 1], elapsed_time);
4661 /* currentMoveString is set as a side-effect of ParseOneMove */
4662 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4663 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4664 strcat(moveList[moveNum - 1], "\n");
4666 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4667 && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4668 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4669 ChessSquare old, new = boards[moveNum][k][j];
4670 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4671 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4672 if(old == new) continue;
4673 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4674 else if(new == WhiteWazir || new == BlackWazir) {
4675 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4676 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4677 else boards[moveNum][k][j] = old; // preserve type of Gold
4678 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4679 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4682 /* Move from ICS was illegal!? Punt. */
4683 if (appData.debugMode) {
4684 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4685 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4687 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4688 strcat(parseList[moveNum - 1], " ");
4689 strcat(parseList[moveNum - 1], elapsed_time);
4690 moveList[moveNum - 1][0] = NULLCHAR;
4691 fromX = fromY = toX = toY = -1;
4694 if (appData.debugMode) {
4695 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4696 setbuf(debugFP, NULL);
4700 /* Send move to chess program (BEFORE animating it). */
4701 if (appData.zippyPlay && !newGame && newMove &&
4702 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4704 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4705 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4706 if (moveList[moveNum - 1][0] == NULLCHAR) {
4707 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4709 DisplayError(str, 0);
4711 if (first.sendTime) {
4712 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4714 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4715 if (firstMove && !bookHit) {
4717 if (first.useColors) {
4718 SendToProgram(gameMode == IcsPlayingWhite ?
4720 "black\ngo\n", &first);
4722 SendToProgram("go\n", &first);
4724 first.maybeThinking = TRUE;
4727 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4728 if (moveList[moveNum - 1][0] == NULLCHAR) {
4729 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4730 DisplayError(str, 0);
4732 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4733 SendMoveToProgram(moveNum - 1, &first);
4740 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4741 /* If move comes from a remote source, animate it. If it
4742 isn't remote, it will have already been animated. */
4743 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4744 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4746 if (!pausing && appData.highlightLastMove) {
4747 SetHighlights(fromX, fromY, toX, toY);
4751 /* Start the clocks */
4752 whiteFlag = blackFlag = FALSE;
4753 appData.clockMode = !(basetime == 0 && increment == 0);
4755 ics_clock_paused = TRUE;
4757 } else if (ticking == 1) {
4758 ics_clock_paused = FALSE;
4760 if (gameMode == IcsIdle ||
4761 relation == RELATION_OBSERVING_STATIC ||
4762 relation == RELATION_EXAMINING ||
4764 DisplayBothClocks();
4768 /* Display opponents and material strengths */
4769 if (gameInfo.variant != VariantBughouse &&
4770 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4771 if (tinyLayout || smallLayout) {
4772 if(gameInfo.variant == VariantNormal)
4773 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4774 gameInfo.white, white_stren, gameInfo.black, black_stren,
4775 basetime, increment);
4777 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4778 gameInfo.white, white_stren, gameInfo.black, black_stren,
4779 basetime, increment, (int) gameInfo.variant);
4781 if(gameInfo.variant == VariantNormal)
4782 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4783 gameInfo.white, white_stren, gameInfo.black, black_stren,
4784 basetime, increment);
4786 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4787 gameInfo.white, white_stren, gameInfo.black, black_stren,
4788 basetime, increment, VariantName(gameInfo.variant));
4791 if (appData.debugMode) {
4792 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4797 /* Display the board */
4798 if (!pausing && !appData.noGUI) {
4800 if (appData.premove)
4802 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4803 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4804 ClearPremoveHighlights();
4806 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4807 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4808 DrawPosition(j, boards[currentMove]);
4810 DisplayMove(moveNum - 1);
4811 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4812 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4813 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4814 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4818 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4820 if(bookHit) { // [HGM] book: simulate book reply
4821 static char bookMove[MSG_SIZ]; // a bit generous?
4823 programStats.nodes = programStats.depth = programStats.time =
4824 programStats.score = programStats.got_only_move = 0;
4825 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4827 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4828 strcat(bookMove, bookHit);
4829 HandleMachineMove(bookMove, &first);
4838 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4839 ics_getting_history = H_REQUESTED;
4840 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4846 AnalysisPeriodicEvent(force)
4849 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4850 && !force) || !appData.periodicUpdates)
4853 /* Send . command to Crafty to collect stats */
4854 SendToProgram(".\n", &first);
4856 /* Don't send another until we get a response (this makes
4857 us stop sending to old Crafty's which don't understand
4858 the "." command (sending illegal cmds resets node count & time,
4859 which looks bad)) */
4860 programStats.ok_to_send = 0;
4863 void ics_update_width(new_width)
4866 ics_printf("set width %d\n", new_width);
4870 SendMoveToProgram(moveNum, cps)
4872 ChessProgramState *cps;
4876 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4877 // null move in variant where engine does not understand it (for analysis purposes)
4878 SendBoard(cps, moveNum + 1); // send position after move in stead.
4881 if (cps->useUsermove) {
4882 SendToProgram("usermove ", cps);
4886 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4887 int len = space - parseList[moveNum];
4888 memcpy(buf, parseList[moveNum], len);
4890 buf[len] = NULLCHAR;
4892 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4894 SendToProgram(buf, cps);
4896 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4897 AlphaRank(moveList[moveNum], 4);
4898 SendToProgram(moveList[moveNum], cps);
4899 AlphaRank(moveList[moveNum], 4); // and back
4901 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4902 * the engine. It would be nice to have a better way to identify castle
4904 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4905 && cps->useOOCastle) {
4906 int fromX = moveList[moveNum][0] - AAA;
4907 int fromY = moveList[moveNum][1] - ONE;
4908 int toX = moveList[moveNum][2] - AAA;
4909 int toY = moveList[moveNum][3] - ONE;
4910 if((boards[moveNum][fromY][fromX] == WhiteKing
4911 && boards[moveNum][toY][toX] == WhiteRook)
4912 || (boards[moveNum][fromY][fromX] == BlackKing
4913 && boards[moveNum][toY][toX] == BlackRook)) {
4914 if(toX > fromX) SendToProgram("O-O\n", cps);
4915 else SendToProgram("O-O-O\n", cps);
4917 else SendToProgram(moveList[moveNum], cps);
4919 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4920 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4921 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4922 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4923 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4926 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4927 SendToProgram(buf, cps);
4929 else SendToProgram(moveList[moveNum], cps);
4930 /* End of additions by Tord */
4933 /* [HGM] setting up the opening has brought engine in force mode! */
4934 /* Send 'go' if we are in a mode where machine should play. */
4935 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4936 (gameMode == TwoMachinesPlay ||
4938 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4940 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4941 SendToProgram("go\n", cps);
4942 if (appData.debugMode) {
4943 fprintf(debugFP, "(extra)\n");
4946 setboardSpoiledMachineBlack = 0;
4950 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4952 int fromX, fromY, toX, toY;
4955 char user_move[MSG_SIZ];
4959 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4960 (int)moveType, fromX, fromY, toX, toY);
4961 DisplayError(user_move + strlen("say "), 0);
4963 case WhiteKingSideCastle:
4964 case BlackKingSideCastle:
4965 case WhiteQueenSideCastleWild:
4966 case BlackQueenSideCastleWild:
4968 case WhiteHSideCastleFR:
4969 case BlackHSideCastleFR:
4971 snprintf(user_move, MSG_SIZ, "o-o\n");
4973 case WhiteQueenSideCastle:
4974 case BlackQueenSideCastle:
4975 case WhiteKingSideCastleWild:
4976 case BlackKingSideCastleWild:
4978 case WhiteASideCastleFR:
4979 case BlackASideCastleFR:
4981 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4983 case WhiteNonPromotion:
4984 case BlackNonPromotion:
4985 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4987 case WhitePromotion:
4988 case BlackPromotion:
4989 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4990 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4991 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4992 PieceToChar(WhiteFerz));
4993 else if(gameInfo.variant == VariantGreat)
4994 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4995 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4996 PieceToChar(WhiteMan));
4998 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4999 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5006 ToUpper(PieceToChar((ChessSquare) fromX)),
5007 AAA + toX, ONE + toY);
5009 case IllegalMove: /* could be a variant we don't quite understand */
5010 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5012 case WhiteCapturesEnPassant:
5013 case BlackCapturesEnPassant:
5014 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5015 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5018 SendToICS(user_move);
5019 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5020 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5025 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5026 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5027 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5028 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5029 DisplayError("You cannot do this while you are playing or observing", 0);
5032 if(gameMode != IcsExamining) { // is this ever not the case?
5033 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5035 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5036 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5037 } else { // on FICS we must first go to general examine mode
5038 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5040 if(gameInfo.variant != VariantNormal) {
5041 // try figure out wild number, as xboard names are not always valid on ICS
5042 for(i=1; i<=36; i++) {
5043 snprintf(buf, MSG_SIZ, "wild/%d", i);
5044 if(StringToVariant(buf) == gameInfo.variant) break;
5046 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5047 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5048 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5049 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5050 SendToICS(ics_prefix);
5052 if(startedFromSetupPosition || backwardMostMove != 0) {
5053 fen = PositionToFEN(backwardMostMove, NULL);
5054 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5055 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5057 } else { // FICS: everything has to set by separate bsetup commands
5058 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5059 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5061 if(!WhiteOnMove(backwardMostMove)) {
5062 SendToICS("bsetup tomove black\n");
5064 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5065 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5067 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5068 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5070 i = boards[backwardMostMove][EP_STATUS];
5071 if(i >= 0) { // set e.p.
5072 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5078 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5079 SendToICS("bsetup done\n"); // switch to normal examining.
5081 for(i = backwardMostMove; i<last; i++) {
5083 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5086 SendToICS(ics_prefix);
5087 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5091 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5096 if (rf == DROP_RANK) {
5097 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5098 sprintf(move, "%c@%c%c\n",
5099 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5101 if (promoChar == 'x' || promoChar == NULLCHAR) {
5102 sprintf(move, "%c%c%c%c\n",
5103 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5105 sprintf(move, "%c%c%c%c%c\n",
5106 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5112 ProcessICSInitScript(f)
5117 while (fgets(buf, MSG_SIZ, f)) {
5118 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5125 static int lastX, lastY, selectFlag, dragging;
5130 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5131 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5132 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5133 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5134 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5135 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5138 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5139 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5140 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5141 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5143 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5144 appData.testLegality && (promoSweep == king ||
5145 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5146 ChangeDragPiece(promoSweep);
5149 int PromoScroll(int x, int y)
5153 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5154 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5155 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5156 if(!step) return FALSE;
5157 lastX = x; lastY = y;
5158 if((promoSweep < BlackPawn) == flipView) step = -step;
5159 if(step > 0) selectFlag = 1;
5160 if(!selectFlag) Sweep(step);
5167 ChessSquare piece = boards[currentMove][toY][toX];
5170 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5171 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5172 if(!step) step = -1;
5173 } while(PieceToChar(pieceSweep) == '.');
5174 boards[currentMove][toY][toX] = pieceSweep;
5175 DrawPosition(FALSE, boards[currentMove]);
5176 boards[currentMove][toY][toX] = piece;
5178 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5180 AlphaRank(char *move, int n)
5182 // char *p = move, c; int x, y;
5184 if (appData.debugMode) {
5185 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5189 move[2]>='0' && move[2]<='9' &&
5190 move[3]>='a' && move[3]<='x' ) {
5192 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5193 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5195 if(move[0]>='0' && move[0]<='9' &&
5196 move[1]>='a' && move[1]<='x' &&
5197 move[2]>='0' && move[2]<='9' &&
5198 move[3]>='a' && move[3]<='x' ) {
5199 /* input move, Shogi -> normal */
5200 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5201 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5202 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5203 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5206 move[3]>='0' && move[3]<='9' &&
5207 move[2]>='a' && move[2]<='x' ) {
5209 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5210 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5213 move[0]>='a' && move[0]<='x' &&
5214 move[3]>='0' && move[3]<='9' &&
5215 move[2]>='a' && move[2]<='x' ) {
5216 /* output move, normal -> Shogi */
5217 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5218 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5219 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5220 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5221 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5223 if (appData.debugMode) {
5224 fprintf(debugFP, " out = '%s'\n", move);
5228 char yy_textstr[8000];
5230 /* Parser for moves from gnuchess, ICS, or user typein box */
5232 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5235 ChessMove *moveType;
5236 int *fromX, *fromY, *toX, *toY;
5239 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5241 switch (*moveType) {
5242 case WhitePromotion:
5243 case BlackPromotion:
5244 case WhiteNonPromotion:
5245 case BlackNonPromotion:
5247 case WhiteCapturesEnPassant:
5248 case BlackCapturesEnPassant:
5249 case WhiteKingSideCastle:
5250 case WhiteQueenSideCastle:
5251 case BlackKingSideCastle:
5252 case BlackQueenSideCastle:
5253 case WhiteKingSideCastleWild:
5254 case WhiteQueenSideCastleWild:
5255 case BlackKingSideCastleWild:
5256 case BlackQueenSideCastleWild:
5257 /* Code added by Tord: */
5258 case WhiteHSideCastleFR:
5259 case WhiteASideCastleFR:
5260 case BlackHSideCastleFR:
5261 case BlackASideCastleFR:
5262 /* End of code added by Tord */
5263 case IllegalMove: /* bug or odd chess variant */
5264 *fromX = currentMoveString[0] - AAA;
5265 *fromY = currentMoveString[1] - ONE;
5266 *toX = currentMoveString[2] - AAA;
5267 *toY = currentMoveString[3] - ONE;
5268 *promoChar = currentMoveString[4];
5269 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5270 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5271 if (appData.debugMode) {
5272 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5274 *fromX = *fromY = *toX = *toY = 0;
5277 if (appData.testLegality) {
5278 return (*moveType != IllegalMove);
5280 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5281 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5286 *fromX = *moveType == WhiteDrop ?
5287 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5288 (int) CharToPiece(ToLower(currentMoveString[0]));
5290 *toX = currentMoveString[2] - AAA;
5291 *toY = currentMoveString[3] - ONE;
5292 *promoChar = NULLCHAR;
5296 case ImpossibleMove:
5306 if (appData.debugMode) {
5307 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5310 *fromX = *fromY = *toX = *toY = 0;
5311 *promoChar = NULLCHAR;
5316 Boolean pushed = FALSE;
5317 char *lastParseAttempt;
5320 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5321 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5322 int fromX, fromY, toX, toY; char promoChar;
5327 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5328 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5331 endPV = forwardMostMove;
5333 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5334 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5335 lastParseAttempt = pv;
5336 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5337 if(appData.debugMode){
5338 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5340 if(!valid && nr == 0 &&
5341 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5342 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5343 // Hande case where played move is different from leading PV move
5344 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5345 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5346 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5347 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5348 endPV += 2; // if position different, keep this
5349 moveList[endPV-1][0] = fromX + AAA;
5350 moveList[endPV-1][1] = fromY + ONE;
5351 moveList[endPV-1][2] = toX + AAA;
5352 moveList[endPV-1][3] = toY + ONE;
5353 parseList[endPV-1][0] = NULLCHAR;
5354 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5357 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5358 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5359 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5360 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5361 valid++; // allow comments in PV
5365 if(endPV+1 > framePtr) break; // no space, truncate
5368 CopyBoard(boards[endPV], boards[endPV-1]);
5369 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5370 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5371 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5372 CoordsToAlgebraic(boards[endPV - 1],
5373 PosFlags(endPV - 1),
5374 fromY, fromX, toY, toX, promoChar,
5375 parseList[endPV - 1]);
5377 if(atEnd == 2) return; // used hidden, for PV conversion
5378 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5379 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5380 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5381 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5382 DrawPosition(TRUE, boards[currentMove]);
5386 MultiPV(ChessProgramState *cps)
5387 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5389 for(i=0; i<cps->nrOptions; i++)
5390 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5396 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5398 int startPV, multi, lineStart, origIndex = index;
5399 char *p, buf2[MSG_SIZ];
5401 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5402 lastX = x; lastY = y;
5403 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5404 lineStart = startPV = index;
5405 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5406 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5408 do{ while(buf[index] && buf[index] != '\n') index++;
5409 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5411 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5412 int n = first.option[multi].value;
5413 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5414 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5415 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5416 first.option[multi].value = n;
5420 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5421 *start = startPV; *end = index-1;
5428 static char buf[10*MSG_SIZ];
5429 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5431 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5432 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5433 for(i = forwardMostMove; i<endPV; i++){
5434 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5435 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5438 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5439 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5445 LoadPV(int x, int y)
5446 { // called on right mouse click to load PV
5447 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5448 lastX = x; lastY = y;
5449 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5456 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5457 if(endPV < 0) return;
5459 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5460 Boolean saveAnimate = appData.animate;
5462 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5463 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5464 } else storedGames--; // abandon shelved tail of original game
5467 forwardMostMove = currentMove;
5468 currentMove = oldFMM;
5469 appData.animate = FALSE;
5470 ToNrEvent(forwardMostMove);
5471 appData.animate = saveAnimate;
5473 currentMove = forwardMostMove;
5474 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5475 ClearPremoveHighlights();
5476 DrawPosition(TRUE, boards[currentMove]);
5480 MovePV(int x, int y, int h)
5481 { // step through PV based on mouse coordinates (called on mouse move)
5482 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5484 // we must somehow check if right button is still down (might be released off board!)
5485 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5486 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5487 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5489 lastX = x; lastY = y;
5491 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5492 if(endPV < 0) return;
5493 if(y < margin) step = 1; else
5494 if(y > h - margin) step = -1;
5495 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5496 currentMove += step;
5497 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5498 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5499 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5500 DrawPosition(FALSE, boards[currentMove]);
5504 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5505 // All positions will have equal probability, but the current method will not provide a unique
5506 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5512 int piecesLeft[(int)BlackPawn];
5513 int seed, nrOfShuffles;
5515 void GetPositionNumber()
5516 { // sets global variable seed
5519 seed = appData.defaultFrcPosition;
5520 if(seed < 0) { // randomize based on time for negative FRC position numbers
5521 for(i=0; i<50; i++) seed += random();
5522 seed = random() ^ random() >> 8 ^ random() << 8;
5523 if(seed<0) seed = -seed;
5527 int put(Board board, int pieceType, int rank, int n, int shade)
5528 // put the piece on the (n-1)-th empty squares of the given shade
5532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5533 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5534 board[rank][i] = (ChessSquare) pieceType;
5535 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5537 piecesLeft[pieceType]--;
5545 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5546 // calculate where the next piece goes, (any empty square), and put it there
5550 i = seed % squaresLeft[shade];
5551 nrOfShuffles *= squaresLeft[shade];
5552 seed /= squaresLeft[shade];
5553 put(board, pieceType, rank, i, shade);
5556 void AddTwoPieces(Board board, int pieceType, int rank)
5557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5559 int i, n=squaresLeft[ANY], j=n-1, k;
5561 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5562 i = seed % k; // pick one
5565 while(i >= j) i -= j--;
5566 j = n - 1 - j; i += j;
5567 put(board, pieceType, rank, j, ANY);
5568 put(board, pieceType, rank, i, ANY);
5571 void SetUpShuffle(Board board, int number)
5575 GetPositionNumber(); nrOfShuffles = 1;
5577 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5578 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5579 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5581 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5583 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5584 p = (int) board[0][i];
5585 if(p < (int) BlackPawn) piecesLeft[p] ++;
5586 board[0][i] = EmptySquare;
5589 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5590 // shuffles restricted to allow normal castling put KRR first
5591 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5592 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5593 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5594 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5595 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5596 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5597 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5598 put(board, WhiteRook, 0, 0, ANY);
5599 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5602 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5603 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5604 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5605 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5606 while(piecesLeft[p] >= 2) {
5607 AddOnePiece(board, p, 0, LITE);
5608 AddOnePiece(board, p, 0, DARK);
5610 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5613 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5614 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5615 // but we leave King and Rooks for last, to possibly obey FRC restriction
5616 if(p == (int)WhiteRook) continue;
5617 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5618 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5621 // now everything is placed, except perhaps King (Unicorn) and Rooks
5623 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5624 // Last King gets castling rights
5625 while(piecesLeft[(int)WhiteUnicorn]) {
5626 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5627 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5630 while(piecesLeft[(int)WhiteKing]) {
5631 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5632 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5637 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5638 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5641 // Only Rooks can be left; simply place them all
5642 while(piecesLeft[(int)WhiteRook]) {
5643 i = put(board, WhiteRook, 0, 0, ANY);
5644 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5647 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5649 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5652 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5653 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5656 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5659 int SetCharTable( char *table, const char * map )
5660 /* [HGM] moved here from winboard.c because of its general usefulness */
5661 /* Basically a safe strcpy that uses the last character as King */
5663 int result = FALSE; int NrPieces;
5665 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5666 && NrPieces >= 12 && !(NrPieces&1)) {
5667 int i; /* [HGM] Accept even length from 12 to 34 */
5669 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5670 for( i=0; i<NrPieces/2-1; i++ ) {
5672 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5674 table[(int) WhiteKing] = map[NrPieces/2-1];
5675 table[(int) BlackKing] = map[NrPieces-1];
5683 void Prelude(Board board)
5684 { // [HGM] superchess: random selection of exo-pieces
5685 int i, j, k; ChessSquare p;
5686 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5688 GetPositionNumber(); // use FRC position number
5690 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5691 SetCharTable(pieceToChar, appData.pieceToCharTable);
5692 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5693 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5696 j = seed%4; seed /= 4;
5697 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5698 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5699 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5700 j = seed%3 + (seed%3 >= j); seed /= 3;
5701 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5703 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5704 j = seed%3; seed /= 3;
5705 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5706 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5707 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5708 j = seed%2 + (seed%2 >= j); seed /= 2;
5709 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5711 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5712 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5713 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5714 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5715 put(board, exoPieces[0], 0, 0, ANY);
5716 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5720 InitPosition(redraw)
5723 ChessSquare (* pieces)[BOARD_FILES];
5724 int i, j, pawnRow, overrule,
5725 oldx = gameInfo.boardWidth,
5726 oldy = gameInfo.boardHeight,
5727 oldh = gameInfo.holdingsWidth;
5730 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5732 /* [AS] Initialize pv info list [HGM] and game status */
5734 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5735 pvInfoList[i].depth = 0;
5736 boards[i][EP_STATUS] = EP_NONE;
5737 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5740 initialRulePlies = 0; /* 50-move counter start */
5742 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5743 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5747 /* [HGM] logic here is completely changed. In stead of full positions */
5748 /* the initialized data only consist of the two backranks. The switch */
5749 /* selects which one we will use, which is than copied to the Board */
5750 /* initialPosition, which for the rest is initialized by Pawns and */
5751 /* empty squares. This initial position is then copied to boards[0], */
5752 /* possibly after shuffling, so that it remains available. */
5754 gameInfo.holdingsWidth = 0; /* default board sizes */
5755 gameInfo.boardWidth = 8;
5756 gameInfo.boardHeight = 8;
5757 gameInfo.holdingsSize = 0;
5758 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5759 for(i=0; i<BOARD_FILES-2; i++)
5760 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5761 initialPosition[EP_STATUS] = EP_NONE;
5762 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5763 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5764 SetCharTable(pieceNickName, appData.pieceNickNames);
5765 else SetCharTable(pieceNickName, "............");
5768 switch (gameInfo.variant) {
5769 case VariantFischeRandom:
5770 shuffleOpenings = TRUE;
5773 case VariantShatranj:
5774 pieces = ShatranjArray;
5775 nrCastlingRights = 0;
5776 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5779 pieces = makrukArray;
5780 nrCastlingRights = 0;
5781 startedFromSetupPosition = TRUE;
5782 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5784 case VariantTwoKings:
5785 pieces = twoKingsArray;
5788 pieces = GrandArray;
5789 nrCastlingRights = 0;
5790 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5791 gameInfo.boardWidth = 10;
5792 gameInfo.boardHeight = 10;
5793 gameInfo.holdingsSize = 7;
5795 case VariantCapaRandom:
5796 shuffleOpenings = TRUE;
5797 case VariantCapablanca:
5798 pieces = CapablancaArray;
5799 gameInfo.boardWidth = 10;
5800 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803 pieces = GothicArray;
5804 gameInfo.boardWidth = 10;
5805 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5809 gameInfo.holdingsSize = 7;
5812 pieces = JanusArray;
5813 gameInfo.boardWidth = 10;
5814 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5815 nrCastlingRights = 6;
5816 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5817 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5818 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5819 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5820 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5821 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5824 pieces = FalconArray;
5825 gameInfo.boardWidth = 10;
5826 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5828 case VariantXiangqi:
5829 pieces = XiangqiArray;
5830 gameInfo.boardWidth = 9;
5831 gameInfo.boardHeight = 10;
5832 nrCastlingRights = 0;
5833 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5836 pieces = ShogiArray;
5837 gameInfo.boardWidth = 9;
5838 gameInfo.boardHeight = 9;
5839 gameInfo.holdingsSize = 7;
5840 nrCastlingRights = 0;
5841 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5843 case VariantCourier:
5844 pieces = CourierArray;
5845 gameInfo.boardWidth = 12;
5846 nrCastlingRights = 0;
5847 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5849 case VariantKnightmate:
5850 pieces = KnightmateArray;
5851 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5853 case VariantSpartan:
5854 pieces = SpartanArray;
5855 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5858 pieces = fairyArray;
5859 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5862 pieces = GreatArray;
5863 gameInfo.boardWidth = 10;
5864 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5865 gameInfo.holdingsSize = 8;
5869 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5870 gameInfo.holdingsSize = 8;
5871 startedFromSetupPosition = TRUE;
5873 case VariantCrazyhouse:
5874 case VariantBughouse:
5876 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5877 gameInfo.holdingsSize = 5;
5879 case VariantWildCastle:
5881 /* !!?shuffle with kings guaranteed to be on d or e file */
5882 shuffleOpenings = 1;
5884 case VariantNoCastle:
5886 nrCastlingRights = 0;
5887 /* !!?unconstrained back-rank shuffle */
5888 shuffleOpenings = 1;
5893 if(appData.NrFiles >= 0) {
5894 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5895 gameInfo.boardWidth = appData.NrFiles;
5897 if(appData.NrRanks >= 0) {
5898 gameInfo.boardHeight = appData.NrRanks;
5900 if(appData.holdingsSize >= 0) {
5901 i = appData.holdingsSize;
5902 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5903 gameInfo.holdingsSize = i;
5905 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5906 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5907 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5909 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5910 if(pawnRow < 1) pawnRow = 1;
5911 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5913 /* User pieceToChar list overrules defaults */
5914 if(appData.pieceToCharTable != NULL)
5915 SetCharTable(pieceToChar, appData.pieceToCharTable);
5917 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5919 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5920 s = (ChessSquare) 0; /* account holding counts in guard band */
5921 for( i=0; i<BOARD_HEIGHT; i++ )
5922 initialPosition[i][j] = s;
5924 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5925 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5926 initialPosition[pawnRow][j] = WhitePawn;
5927 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5928 if(gameInfo.variant == VariantXiangqi) {
5930 initialPosition[pawnRow][j] =
5931 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5932 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5933 initialPosition[2][j] = WhiteCannon;
5934 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5938 if(gameInfo.variant == VariantGrand) {
5939 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5940 initialPosition[0][j] = WhiteRook;
5941 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5944 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5946 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5949 initialPosition[1][j] = WhiteBishop;
5950 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5952 initialPosition[1][j] = WhiteRook;
5953 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5956 if( nrCastlingRights == -1) {
5957 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5958 /* This sets default castling rights from none to normal corners */
5959 /* Variants with other castling rights must set them themselves above */
5960 nrCastlingRights = 6;
5962 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5963 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5964 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5965 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5966 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5967 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5970 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5971 if(gameInfo.variant == VariantGreat) { // promotion commoners
5972 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5973 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5974 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5975 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5977 if( gameInfo.variant == VariantSChess ) {
5978 initialPosition[1][0] = BlackMarshall;
5979 initialPosition[2][0] = BlackAngel;
5980 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5981 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5982 initialPosition[1][1] = initialPosition[2][1] =
5983 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5985 if (appData.debugMode) {
5986 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5988 if(shuffleOpenings) {
5989 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5990 startedFromSetupPosition = TRUE;
5992 if(startedFromPositionFile) {
5993 /* [HGM] loadPos: use PositionFile for every new game */
5994 CopyBoard(initialPosition, filePosition);
5995 for(i=0; i<nrCastlingRights; i++)
5996 initialRights[i] = filePosition[CASTLING][i];
5997 startedFromSetupPosition = TRUE;
6000 CopyBoard(boards[0], initialPosition);
6002 if(oldx != gameInfo.boardWidth ||
6003 oldy != gameInfo.boardHeight ||
6004 oldv != gameInfo.variant ||
6005 oldh != gameInfo.holdingsWidth
6007 InitDrawingSizes(-2 ,0);
6009 oldv = gameInfo.variant;
6011 DrawPosition(TRUE, boards[currentMove]);
6015 SendBoard(cps, moveNum)
6016 ChessProgramState *cps;
6019 char message[MSG_SIZ];
6021 if (cps->useSetboard) {
6022 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6023 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6024 SendToProgram(message, cps);
6030 /* Kludge to set black to move, avoiding the troublesome and now
6031 * deprecated "black" command.
6033 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6034 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6036 SendToProgram("edit\n", cps);
6037 SendToProgram("#\n", cps);
6038 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6039 bp = &boards[moveNum][i][BOARD_LEFT];
6040 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6041 if ((int) *bp < (int) BlackPawn) {
6042 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6044 if(message[0] == '+' || message[0] == '~') {
6045 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6046 PieceToChar((ChessSquare)(DEMOTED *bp)),
6049 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6050 message[1] = BOARD_RGHT - 1 - j + '1';
6051 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6053 SendToProgram(message, cps);
6058 SendToProgram("c\n", cps);
6059 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6060 bp = &boards[moveNum][i][BOARD_LEFT];
6061 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6062 if (((int) *bp != (int) EmptySquare)
6063 && ((int) *bp >= (int) BlackPawn)) {
6064 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6066 if(message[0] == '+' || message[0] == '~') {
6067 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6068 PieceToChar((ChessSquare)(DEMOTED *bp)),
6071 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6072 message[1] = BOARD_RGHT - 1 - j + '1';
6073 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6075 SendToProgram(message, cps);
6080 SendToProgram(".\n", cps);
6082 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6086 DefaultPromoChoice(int white)
6089 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6090 result = WhiteFerz; // no choice
6091 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6092 result= WhiteKing; // in Suicide Q is the last thing we want
6093 else if(gameInfo.variant == VariantSpartan)
6094 result = white ? WhiteQueen : WhiteAngel;
6095 else result = WhiteQueen;
6096 if(!white) result = WHITE_TO_BLACK result;
6100 static int autoQueen; // [HGM] oneclick
6103 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6105 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6106 /* [HGM] add Shogi promotions */
6107 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6112 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6113 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6115 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6116 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6119 piece = boards[currentMove][fromY][fromX];
6120 if(gameInfo.variant == VariantShogi) {
6121 promotionZoneSize = BOARD_HEIGHT/3;
6122 highestPromotingPiece = (int)WhiteFerz;
6123 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6124 promotionZoneSize = 3;
6127 // Treat Lance as Pawn when it is not representing Amazon
6128 if(gameInfo.variant != VariantSuper) {
6129 if(piece == WhiteLance) piece = WhitePawn; else
6130 if(piece == BlackLance) piece = BlackPawn;
6133 // next weed out all moves that do not touch the promotion zone at all
6134 if((int)piece >= BlackPawn) {
6135 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6137 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6139 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6140 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6143 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6145 // weed out mandatory Shogi promotions
6146 if(gameInfo.variant == VariantShogi) {
6147 if(piece >= BlackPawn) {
6148 if(toY == 0 && piece == BlackPawn ||
6149 toY == 0 && piece == BlackQueen ||
6150 toY <= 1 && piece == BlackKnight) {
6155 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6156 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6157 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6164 // weed out obviously illegal Pawn moves
6165 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6166 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6167 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6168 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6169 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6170 // note we are not allowed to test for valid (non-)capture, due to premove
6173 // we either have a choice what to promote to, or (in Shogi) whether to promote
6174 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6175 *promoChoice = PieceToChar(BlackFerz); // no choice
6178 // no sense asking what we must promote to if it is going to explode...
6179 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6180 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6183 // give caller the default choice even if we will not make it
6184 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6185 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6186 if( sweepSelect && gameInfo.variant != VariantGreat
6187 && gameInfo.variant != VariantGrand
6188 && gameInfo.variant != VariantSuper) return FALSE;
6189 if(autoQueen) return FALSE; // predetermined
6191 // suppress promotion popup on illegal moves that are not premoves
6192 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6193 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6194 if(appData.testLegality && !premove) {
6195 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6196 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6197 if(moveType != WhitePromotion && moveType != BlackPromotion)
6205 InPalace(row, column)
6207 { /* [HGM] for Xiangqi */
6208 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6209 column < (BOARD_WIDTH + 4)/2 &&
6210 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6215 PieceForSquare (x, y)
6219 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6222 return boards[currentMove][y][x];
6226 OKToStartUserMove(x, y)
6229 ChessSquare from_piece;
6232 if (matchMode) return FALSE;
6233 if (gameMode == EditPosition) return TRUE;
6235 if (x >= 0 && y >= 0)
6236 from_piece = boards[currentMove][y][x];
6238 from_piece = EmptySquare;
6240 if (from_piece == EmptySquare) return FALSE;
6242 white_piece = (int)from_piece >= (int)WhitePawn &&
6243 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6247 case TwoMachinesPlay:
6255 case MachinePlaysWhite:
6256 case IcsPlayingBlack:
6257 if (appData.zippyPlay) return FALSE;
6259 DisplayMoveError(_("You are playing Black"));
6264 case MachinePlaysBlack:
6265 case IcsPlayingWhite:
6266 if (appData.zippyPlay) return FALSE;
6268 DisplayMoveError(_("You are playing White"));
6273 case PlayFromGameFile:
6274 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6276 if (!white_piece && WhiteOnMove(currentMove)) {
6277 DisplayMoveError(_("It is White's turn"));
6280 if (white_piece && !WhiteOnMove(currentMove)) {
6281 DisplayMoveError(_("It is Black's turn"));
6284 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6285 /* Editing correspondence game history */
6286 /* Could disallow this or prompt for confirmation */
6291 case BeginningOfGame:
6292 if (appData.icsActive) return FALSE;
6293 if (!appData.noChessProgram) {
6295 DisplayMoveError(_("You are playing White"));
6302 if (!white_piece && WhiteOnMove(currentMove)) {
6303 DisplayMoveError(_("It is White's turn"));
6306 if (white_piece && !WhiteOnMove(currentMove)) {
6307 DisplayMoveError(_("It is Black's turn"));
6316 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6317 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6318 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6319 && gameMode != AnalyzeFile && gameMode != Training) {
6320 DisplayMoveError(_("Displayed position is not current"));
6327 OnlyMove(int *x, int *y, Boolean captures) {
6328 DisambiguateClosure cl;
6329 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6331 case MachinePlaysBlack:
6332 case IcsPlayingWhite:
6333 case BeginningOfGame:
6334 if(!WhiteOnMove(currentMove)) return FALSE;
6336 case MachinePlaysWhite:
6337 case IcsPlayingBlack:
6338 if(WhiteOnMove(currentMove)) return FALSE;
6345 cl.pieceIn = EmptySquare;
6350 cl.promoCharIn = NULLCHAR;
6351 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6352 if( cl.kind == NormalMove ||
6353 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6354 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6355 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6362 if(cl.kind != ImpossibleMove) return FALSE;
6363 cl.pieceIn = EmptySquare;
6368 cl.promoCharIn = NULLCHAR;
6369 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6370 if( cl.kind == NormalMove ||
6371 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6372 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6373 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6385 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6386 int lastLoadGameUseList = FALSE;
6387 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6388 ChessMove lastLoadGameStart = EndOfFile;
6391 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6392 int fromX, fromY, toX, toY;
6396 ChessSquare pdown, pup;
6398 /* Check if the user is playing in turn. This is complicated because we
6399 let the user "pick up" a piece before it is his turn. So the piece he
6400 tried to pick up may have been captured by the time he puts it down!
6401 Therefore we use the color the user is supposed to be playing in this
6402 test, not the color of the piece that is currently on the starting
6403 square---except in EditGame mode, where the user is playing both
6404 sides; fortunately there the capture race can't happen. (It can
6405 now happen in IcsExamining mode, but that's just too bad. The user
6406 will get a somewhat confusing message in that case.)
6411 case TwoMachinesPlay:
6415 /* We switched into a game mode where moves are not accepted,
6416 perhaps while the mouse button was down. */
6419 case MachinePlaysWhite:
6420 /* User is moving for Black */
6421 if (WhiteOnMove(currentMove)) {
6422 DisplayMoveError(_("It is White's turn"));
6427 case MachinePlaysBlack:
6428 /* User is moving for White */
6429 if (!WhiteOnMove(currentMove)) {
6430 DisplayMoveError(_("It is Black's turn"));
6435 case PlayFromGameFile:
6436 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6439 case BeginningOfGame:
6442 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6443 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6444 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6445 /* User is moving for Black */
6446 if (WhiteOnMove(currentMove)) {
6447 DisplayMoveError(_("It is White's turn"));
6451 /* User is moving for White */
6452 if (!WhiteOnMove(currentMove)) {
6453 DisplayMoveError(_("It is Black's turn"));
6459 case IcsPlayingBlack:
6460 /* User is moving for Black */
6461 if (WhiteOnMove(currentMove)) {
6462 if (!appData.premove) {
6463 DisplayMoveError(_("It is White's turn"));
6464 } else if (toX >= 0 && toY >= 0) {
6467 premoveFromX = fromX;
6468 premoveFromY = fromY;
6469 premovePromoChar = promoChar;
6471 if (appData.debugMode)
6472 fprintf(debugFP, "Got premove: fromX %d,"
6473 "fromY %d, toX %d, toY %d\n",
6474 fromX, fromY, toX, toY);
6480 case IcsPlayingWhite:
6481 /* User is moving for White */
6482 if (!WhiteOnMove(currentMove)) {
6483 if (!appData.premove) {
6484 DisplayMoveError(_("It is Black's turn"));
6485 } else if (toX >= 0 && toY >= 0) {
6488 premoveFromX = fromX;
6489 premoveFromY = fromY;
6490 premovePromoChar = promoChar;
6492 if (appData.debugMode)
6493 fprintf(debugFP, "Got premove: fromX %d,"
6494 "fromY %d, toX %d, toY %d\n",
6495 fromX, fromY, toX, toY);
6505 /* EditPosition, empty square, or different color piece;
6506 click-click move is possible */
6507 if (toX == -2 || toY == -2) {
6508 boards[0][fromY][fromX] = EmptySquare;
6509 DrawPosition(FALSE, boards[currentMove]);
6511 } else if (toX >= 0 && toY >= 0) {
6512 boards[0][toY][toX] = boards[0][fromY][fromX];
6513 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6514 if(boards[0][fromY][0] != EmptySquare) {
6515 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6516 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6519 if(fromX == BOARD_RGHT+1) {
6520 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6521 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6522 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6525 boards[0][fromY][fromX] = EmptySquare;
6526 DrawPosition(FALSE, boards[currentMove]);
6532 if(toX < 0 || toY < 0) return;
6533 pdown = boards[currentMove][fromY][fromX];
6534 pup = boards[currentMove][toY][toX];
6536 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6537 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6538 if( pup != EmptySquare ) return;
6539 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6540 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6541 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6542 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6543 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6544 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6545 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6549 /* [HGM] always test for legality, to get promotion info */
6550 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6551 fromY, fromX, toY, toX, promoChar);
6553 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6555 /* [HGM] but possibly ignore an IllegalMove result */
6556 if (appData.testLegality) {
6557 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6558 DisplayMoveError(_("Illegal move"));
6563 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6566 /* Common tail of UserMoveEvent and DropMenuEvent */
6568 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6570 int fromX, fromY, toX, toY;
6571 /*char*/int promoChar;
6575 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6576 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6577 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6578 if(WhiteOnMove(currentMove)) {
6579 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6581 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6585 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6586 move type in caller when we know the move is a legal promotion */
6587 if(moveType == NormalMove && promoChar)
6588 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6590 /* [HGM] <popupFix> The following if has been moved here from
6591 UserMoveEvent(). Because it seemed to belong here (why not allow
6592 piece drops in training games?), and because it can only be
6593 performed after it is known to what we promote. */
6594 if (gameMode == Training) {
6595 /* compare the move played on the board to the next move in the
6596 * game. If they match, display the move and the opponent's response.
6597 * If they don't match, display an error message.
6601 CopyBoard(testBoard, boards[currentMove]);
6602 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6604 if (CompareBoards(testBoard, boards[currentMove+1])) {
6605 ForwardInner(currentMove+1);
6607 /* Autoplay the opponent's response.
6608 * if appData.animate was TRUE when Training mode was entered,
6609 * the response will be animated.
6611 saveAnimate = appData.animate;
6612 appData.animate = animateTraining;
6613 ForwardInner(currentMove+1);
6614 appData.animate = saveAnimate;
6616 /* check for the end of the game */
6617 if (currentMove >= forwardMostMove) {
6618 gameMode = PlayFromGameFile;
6620 SetTrainingModeOff();
6621 DisplayInformation(_("End of game"));
6624 DisplayError(_("Incorrect move"), 0);
6629 /* Ok, now we know that the move is good, so we can kill
6630 the previous line in Analysis Mode */
6631 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6632 && currentMove < forwardMostMove) {
6633 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6634 else forwardMostMove = currentMove;
6637 /* If we need the chess program but it's dead, restart it */
6638 ResurrectChessProgram();
6640 /* A user move restarts a paused game*/
6644 thinkOutput[0] = NULLCHAR;
6646 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6648 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6649 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6653 if (gameMode == BeginningOfGame) {
6654 if (appData.noChessProgram) {
6655 gameMode = EditGame;
6659 gameMode = MachinePlaysBlack;
6662 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6664 if (first.sendName) {
6665 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6666 SendToProgram(buf, &first);
6673 /* Relay move to ICS or chess engine */
6674 if (appData.icsActive) {
6675 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6676 gameMode == IcsExamining) {
6677 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6678 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6680 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6682 // also send plain move, in case ICS does not understand atomic claims
6683 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6687 if (first.sendTime && (gameMode == BeginningOfGame ||
6688 gameMode == MachinePlaysWhite ||
6689 gameMode == MachinePlaysBlack)) {
6690 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6692 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6693 // [HGM] book: if program might be playing, let it use book
6694 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6695 first.maybeThinking = TRUE;
6696 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6697 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6698 SendBoard(&first, currentMove+1);
6699 } else SendMoveToProgram(forwardMostMove-1, &first);
6700 if (currentMove == cmailOldMove + 1) {
6701 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6705 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6709 if(appData.testLegality)
6710 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6716 if (WhiteOnMove(currentMove)) {
6717 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6719 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6723 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6728 case MachinePlaysBlack:
6729 case MachinePlaysWhite:
6730 /* disable certain menu options while machine is thinking */
6731 SetMachineThinkingEnables();
6738 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6739 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6741 if(bookHit) { // [HGM] book: simulate book reply
6742 static char bookMove[MSG_SIZ]; // a bit generous?
6744 programStats.nodes = programStats.depth = programStats.time =
6745 programStats.score = programStats.got_only_move = 0;
6746 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6748 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6749 strcat(bookMove, bookHit);
6750 HandleMachineMove(bookMove, &first);
6756 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6763 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6764 Markers *m = (Markers *) closure;
6765 if(rf == fromY && ff == fromX)
6766 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6767 || kind == WhiteCapturesEnPassant
6768 || kind == BlackCapturesEnPassant);
6769 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6773 MarkTargetSquares(int clear)
6776 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6777 !appData.testLegality || gameMode == EditPosition) return;
6779 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6782 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6783 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6784 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6786 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6789 DrawPosition(TRUE, NULL);
6793 Explode(Board board, int fromX, int fromY, int toX, int toY)
6795 if(gameInfo.variant == VariantAtomic &&
6796 (board[toY][toX] != EmptySquare || // capture?
6797 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6798 board[fromY][fromX] == BlackPawn )
6800 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6806 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6808 int CanPromote(ChessSquare piece, int y)
6810 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6811 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6812 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6813 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6814 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6815 gameInfo.variant == VariantMakruk) return FALSE;
6816 return (piece == BlackPawn && y == 1 ||
6817 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6818 piece == BlackLance && y == 1 ||
6819 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6822 void LeftClick(ClickType clickType, int xPix, int yPix)
6825 Boolean saveAnimate;
6826 static int second = 0, promotionChoice = 0, clearFlag = 0;
6827 char promoChoice = NULLCHAR;
6830 if(appData.seekGraph && appData.icsActive && loggedOn &&
6831 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6832 SeekGraphClick(clickType, xPix, yPix, 0);
6836 if (clickType == Press) ErrorPopDown();
6838 x = EventToSquare(xPix, BOARD_WIDTH);
6839 y = EventToSquare(yPix, BOARD_HEIGHT);
6840 if (!flipView && y >= 0) {
6841 y = BOARD_HEIGHT - 1 - y;
6843 if (flipView && x >= 0) {
6844 x = BOARD_WIDTH - 1 - x;
6847 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6848 defaultPromoChoice = promoSweep;
6849 promoSweep = EmptySquare; // terminate sweep
6850 promoDefaultAltered = TRUE;
6851 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6854 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6855 if(clickType == Release) return; // ignore upclick of click-click destination
6856 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6857 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6858 if(gameInfo.holdingsWidth &&
6859 (WhiteOnMove(currentMove)
6860 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6861 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6862 // click in right holdings, for determining promotion piece
6863 ChessSquare p = boards[currentMove][y][x];
6864 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6865 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6866 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6867 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6872 DrawPosition(FALSE, boards[currentMove]);
6876 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6877 if(clickType == Press
6878 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6879 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6880 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6883 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6884 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6886 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6887 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6888 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6889 defaultPromoChoice = DefaultPromoChoice(side);
6892 autoQueen = appData.alwaysPromoteToQueen;
6896 gatingPiece = EmptySquare;
6897 if (clickType != Press) {
6898 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6899 DragPieceEnd(xPix, yPix); dragging = 0;
6900 DrawPosition(FALSE, NULL);
6904 fromX = x; fromY = y; toX = toY = -1;
6905 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6906 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6907 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6909 if (OKToStartUserMove(fromX, fromY)) {
6911 MarkTargetSquares(0);
6912 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6913 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6914 promoSweep = defaultPromoChoice;
6915 selectFlag = 0; lastX = xPix; lastY = yPix;
6916 Sweep(0); // Pawn that is going to promote: preview promotion piece
6917 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6919 if (appData.highlightDragging) {
6920 SetHighlights(fromX, fromY, -1, -1);
6922 } else fromX = fromY = -1;
6928 if (clickType == Press && gameMode != EditPosition) {
6933 // ignore off-board to clicks
6934 if(y < 0 || x < 0) return;
6936 /* Check if clicking again on the same color piece */
6937 fromP = boards[currentMove][fromY][fromX];
6938 toP = boards[currentMove][y][x];
6939 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6940 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6941 WhitePawn <= toP && toP <= WhiteKing &&
6942 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6943 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6944 (BlackPawn <= fromP && fromP <= BlackKing &&
6945 BlackPawn <= toP && toP <= BlackKing &&
6946 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6947 !(fromP == BlackKing && toP == BlackRook && frc))) {
6948 /* Clicked again on same color piece -- changed his mind */
6949 second = (x == fromX && y == fromY);
6950 promoDefaultAltered = FALSE;
6951 MarkTargetSquares(1);
6952 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6953 if (appData.highlightDragging) {
6954 SetHighlights(x, y, -1, -1);
6958 if (OKToStartUserMove(x, y)) {
6959 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6960 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6961 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6962 gatingPiece = boards[currentMove][fromY][fromX];
6963 else gatingPiece = EmptySquare;
6965 fromY = y; dragging = 1;
6966 MarkTargetSquares(0);
6967 DragPieceBegin(xPix, yPix, FALSE);
6968 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6969 promoSweep = defaultPromoChoice;
6970 selectFlag = 0; lastX = xPix; lastY = yPix;
6971 Sweep(0); // Pawn that is going to promote: preview promotion piece
6975 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6978 // ignore clicks on holdings
6979 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6982 if (clickType == Release && x == fromX && y == fromY) {
6983 DragPieceEnd(xPix, yPix); dragging = 0;
6985 // a deferred attempt to click-click move an empty square on top of a piece
6986 boards[currentMove][y][x] = EmptySquare;
6988 DrawPosition(FALSE, boards[currentMove]);
6989 fromX = fromY = -1; clearFlag = 0;
6992 if (appData.animateDragging) {
6993 /* Undo animation damage if any */
6994 DrawPosition(FALSE, NULL);
6997 /* Second up/down in same square; just abort move */
7000 gatingPiece = EmptySquare;
7003 ClearPremoveHighlights();
7005 /* First upclick in same square; start click-click mode */
7006 SetHighlights(x, y, -1, -1);
7013 /* we now have a different from- and (possibly off-board) to-square */
7014 /* Completed move */
7017 saveAnimate = appData.animate;
7018 MarkTargetSquares(1);
7019 if (clickType == Press) {
7020 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7021 // must be Edit Position mode with empty-square selected
7022 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7023 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7026 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7027 ChessSquare piece = boards[currentMove][fromY][fromX];
7028 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7029 promoSweep = defaultPromoChoice;
7030 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7031 selectFlag = 0; lastX = xPix; lastY = yPix;
7032 Sweep(0); // Pawn that is going to promote: preview promotion piece
7033 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7034 DrawPosition(FALSE, boards[currentMove]);
7037 /* Finish clickclick move */
7038 if (appData.animate || appData.highlightLastMove) {
7039 SetHighlights(fromX, fromY, toX, toY);
7044 /* Finish drag move */
7045 if (appData.highlightLastMove) {
7046 SetHighlights(fromX, fromY, toX, toY);
7050 DragPieceEnd(xPix, yPix); dragging = 0;
7051 /* Don't animate move and drag both */
7052 appData.animate = FALSE;
7055 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7056 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7057 ChessSquare piece = boards[currentMove][fromY][fromX];
7058 if(gameMode == EditPosition && piece != EmptySquare &&
7059 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7062 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7063 n = PieceToNumber(piece - (int)BlackPawn);
7064 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7065 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7066 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7068 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7069 n = PieceToNumber(piece);
7070 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7071 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7072 boards[currentMove][n][BOARD_WIDTH-2]++;
7074 boards[currentMove][fromY][fromX] = EmptySquare;
7078 DrawPosition(TRUE, boards[currentMove]);
7082 // off-board moves should not be highlighted
7083 if(x < 0 || y < 0) ClearHighlights();
7085 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7087 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7088 SetHighlights(fromX, fromY, toX, toY);
7089 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7090 // [HGM] super: promotion to captured piece selected from holdings
7091 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7092 promotionChoice = TRUE;
7093 // kludge follows to temporarily execute move on display, without promoting yet
7094 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7095 boards[currentMove][toY][toX] = p;
7096 DrawPosition(FALSE, boards[currentMove]);
7097 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7098 boards[currentMove][toY][toX] = q;
7099 DisplayMessage("Click in holdings to choose piece", "");
7104 int oldMove = currentMove;
7105 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7106 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7107 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7108 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7109 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7110 DrawPosition(TRUE, boards[currentMove]);
7113 appData.animate = saveAnimate;
7114 if (appData.animate || appData.animateDragging) {
7115 /* Undo animation damage if needed */
7116 DrawPosition(FALSE, NULL);
7120 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7121 { // front-end-free part taken out of PieceMenuPopup
7122 int whichMenu; int xSqr, ySqr;
7124 if(seekGraphUp) { // [HGM] seekgraph
7125 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7126 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7130 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7131 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7132 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7133 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7134 if(action == Press) {
7135 originalFlip = flipView;
7136 flipView = !flipView; // temporarily flip board to see game from partners perspective
7137 DrawPosition(TRUE, partnerBoard);
7138 DisplayMessage(partnerStatus, "");
7140 } else if(action == Release) {
7141 flipView = originalFlip;
7142 DrawPosition(TRUE, boards[currentMove]);
7148 xSqr = EventToSquare(x, BOARD_WIDTH);
7149 ySqr = EventToSquare(y, BOARD_HEIGHT);
7150 if (action == Release) {
7151 if(pieceSweep != EmptySquare) {
7152 EditPositionMenuEvent(pieceSweep, toX, toY);
7153 pieceSweep = EmptySquare;
7154 } else UnLoadPV(); // [HGM] pv
7156 if (action != Press) return -2; // return code to be ignored
7159 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7161 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7162 if (xSqr < 0 || ySqr < 0) return -1;
7163 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7164 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7165 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7166 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7170 if(!appData.icsEngineAnalyze) return -1;
7171 case IcsPlayingWhite:
7172 case IcsPlayingBlack:
7173 if(!appData.zippyPlay) goto noZip;
7176 case MachinePlaysWhite:
7177 case MachinePlaysBlack:
7178 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7179 if (!appData.dropMenu) {
7181 return 2; // flag front-end to grab mouse events
7183 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7184 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7187 if (xSqr < 0 || ySqr < 0) return -1;
7188 if (!appData.dropMenu || appData.testLegality &&
7189 gameInfo.variant != VariantBughouse &&
7190 gameInfo.variant != VariantCrazyhouse) return -1;
7191 whichMenu = 1; // drop menu
7197 if (((*fromX = xSqr) < 0) ||
7198 ((*fromY = ySqr) < 0)) {
7199 *fromX = *fromY = -1;
7203 *fromX = BOARD_WIDTH - 1 - *fromX;
7205 *fromY = BOARD_HEIGHT - 1 - *fromY;
7210 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7212 // char * hint = lastHint;
7213 FrontEndProgramStats stats;
7215 stats.which = cps == &first ? 0 : 1;
7216 stats.depth = cpstats->depth;
7217 stats.nodes = cpstats->nodes;
7218 stats.score = cpstats->score;
7219 stats.time = cpstats->time;
7220 stats.pv = cpstats->movelist;
7221 stats.hint = lastHint;
7222 stats.an_move_index = 0;
7223 stats.an_move_count = 0;
7225 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7226 stats.hint = cpstats->move_name;
7227 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7228 stats.an_move_count = cpstats->nr_moves;
7231 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
7233 SetProgramStats( &stats );
7237 ClearEngineOutputPane(int which)
7239 static FrontEndProgramStats dummyStats;
7240 dummyStats.which = which;
7241 dummyStats.pv = "#";
7242 SetProgramStats( &dummyStats );
7245 #define MAXPLAYERS 500
7248 TourneyStandings(int display)
7250 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7251 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7252 char result, *p, *names[MAXPLAYERS];
7254 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7255 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7256 names[0] = p = strdup(appData.participants);
7257 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7259 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7261 while(result = appData.results[nr]) {
7262 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7263 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7264 wScore = bScore = 0;
7266 case '+': wScore = 2; break;
7267 case '-': bScore = 2; break;
7268 case '=': wScore = bScore = 1; break;
7270 case '*': return strdup("busy"); // tourney not finished
7278 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7279 for(w=0; w<nPlayers; w++) {
7281 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7282 ranking[w] = b; points[w] = bScore; score[b] = -2;
7284 p = malloc(nPlayers*34+1);
7285 for(w=0; w<nPlayers && w<display; w++)
7286 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7292 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7293 { // count all piece types
7295 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7296 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7297 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7300 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7301 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7302 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7303 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7304 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7305 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7310 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7312 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7313 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7315 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7316 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7317 if(myPawns == 2 && nMine == 3) // KPP
7318 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7319 if(myPawns == 1 && nMine == 2) // KP
7320 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7321 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7322 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7323 if(myPawns) return FALSE;
7324 if(pCnt[WhiteRook+side])
7325 return pCnt[BlackRook-side] ||
7326 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7327 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7328 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7329 if(pCnt[WhiteCannon+side]) {
7330 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7331 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7333 if(pCnt[WhiteKnight+side])
7334 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7339 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7341 VariantClass v = gameInfo.variant;
7343 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7344 if(v == VariantShatranj) return TRUE; // always winnable through baring
7345 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7346 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7348 if(v == VariantXiangqi) {
7349 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7351 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7352 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7353 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7354 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7355 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7356 if(stale) // we have at least one last-rank P plus perhaps C
7357 return majors // KPKX
7358 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7360 return pCnt[WhiteFerz+side] // KCAK
7361 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7362 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7363 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7365 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7366 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7368 if(nMine == 1) return FALSE; // bare King
7369 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
7370 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7371 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7372 // by now we have King + 1 piece (or multiple Bishops on the same color)
7373 if(pCnt[WhiteKnight+side])
7374 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7375 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7376 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7378 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7379 if(pCnt[WhiteAlfil+side])
7380 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7381 if(pCnt[WhiteWazir+side])
7382 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7389 CompareWithRights(Board b1, Board b2)
7392 if(!CompareBoards(b1, b2)) return FALSE;
7393 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7394 /* compare castling rights */
7395 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7396 rights++; /* King lost rights, while rook still had them */
7397 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7398 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7399 rights++; /* but at least one rook lost them */
7401 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7403 if( b1[CASTLING][5] != NoRights ) {
7404 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7411 Adjudicate(ChessProgramState *cps)
7412 { // [HGM] some adjudications useful with buggy engines
7413 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7414 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7415 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7416 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7417 int k, count = 0; static int bare = 1;
7418 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7419 Boolean canAdjudicate = !appData.icsActive;
7421 // most tests only when we understand the game, i.e. legality-checking on
7422 if( appData.testLegality )
7423 { /* [HGM] Some more adjudications for obstinate engines */
7424 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7425 static int moveCount = 6;
7427 char *reason = NULL;
7429 /* Count what is on board. */
7430 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7432 /* Some material-based adjudications that have to be made before stalemate test */
7433 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7434 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7435 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7436 if(canAdjudicate && appData.checkMates) {
7438 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7439 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7440 "Xboard adjudication: King destroyed", GE_XBOARD );
7445 /* Bare King in Shatranj (loses) or Losers (wins) */
7446 if( nrW == 1 || nrB == 1) {
7447 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7448 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7449 if(canAdjudicate && appData.checkMates) {
7451 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7452 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7453 "Xboard adjudication: Bare king", GE_XBOARD );
7457 if( gameInfo.variant == VariantShatranj && --bare < 0)
7459 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7460 if(canAdjudicate && appData.checkMates) {
7461 /* but only adjudicate if adjudication enabled */
7463 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7464 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7465 "Xboard adjudication: Bare king", GE_XBOARD );
7472 // don't wait for engine to announce game end if we can judge ourselves
7473 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7475 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7476 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7477 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7478 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7481 reason = "Xboard adjudication: 3rd check";
7482 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7492 reason = "Xboard adjudication: Stalemate";
7493 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7494 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7495 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7496 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7497 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7498 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7499 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7500 EP_CHECKMATE : EP_WINS);
7501 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7502 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7506 reason = "Xboard adjudication: Checkmate";
7507 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7511 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7513 result = GameIsDrawn; break;
7515 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7517 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7521 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7523 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7524 GameEnds( result, reason, GE_XBOARD );
7528 /* Next absolutely insufficient mating material. */
7529 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7530 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7531 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7533 /* always flag draws, for judging claims */
7534 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7536 if(canAdjudicate && appData.materialDraws) {
7537 /* but only adjudicate them if adjudication enabled */
7538 if(engineOpponent) {
7539 SendToProgram("force\n", engineOpponent); // suppress reply
7540 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7542 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7547 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7548 if(gameInfo.variant == VariantXiangqi ?
7549 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7551 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7552 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7553 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7554 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7556 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7557 { /* if the first 3 moves do not show a tactical win, declare draw */
7558 if(engineOpponent) {
7559 SendToProgram("force\n", engineOpponent); // suppress reply
7560 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7562 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7565 } else moveCount = 6;
7567 if (appData.debugMode) { int i;
7568 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7569 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7570 appData.drawRepeats);
7571 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7572 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7576 // Repetition draws and 50-move rule can be applied independently of legality testing
7578 /* Check for rep-draws */
7580 for(k = forwardMostMove-2;
7581 k>=backwardMostMove && k>=forwardMostMove-100 &&
7582 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7583 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7586 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7587 /* compare castling rights */
7588 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7589 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7590 rights++; /* King lost rights, while rook still had them */
7591 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7592 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7593 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7594 rights++; /* but at least one rook lost them */
7596 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7597 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7599 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7600 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7601 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7604 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7605 && appData.drawRepeats > 1) {
7606 /* adjudicate after user-specified nr of repeats */
7607 int result = GameIsDrawn;
7608 char *details = "XBoard adjudication: repetition draw";
7609 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7610 // [HGM] xiangqi: check for forbidden perpetuals
7611 int m, ourPerpetual = 1, hisPerpetual = 1;
7612 for(m=forwardMostMove; m>k; m-=2) {
7613 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7614 ourPerpetual = 0; // the current mover did not always check
7615 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7616 hisPerpetual = 0; // the opponent did not always check
7618 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7619 ourPerpetual, hisPerpetual);
7620 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7621 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7622 details = "Xboard adjudication: perpetual checking";
7624 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7625 break; // (or we would have caught him before). Abort repetition-checking loop.
7627 // Now check for perpetual chases
7628 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7629 hisPerpetual = PerpetualChase(k, forwardMostMove);
7630 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7631 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7632 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7633 details = "Xboard adjudication: perpetual chasing";
7635 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7636 break; // Abort repetition-checking loop.
7638 // if neither of us is checking or chasing all the time, or both are, it is draw
7640 if(engineOpponent) {
7641 SendToProgram("force\n", engineOpponent); // suppress reply
7642 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7644 GameEnds( result, details, GE_XBOARD );
7647 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7648 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7652 /* Now we test for 50-move draws. Determine ply count */
7653 count = forwardMostMove;
7654 /* look for last irreversble move */
7655 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7657 /* if we hit starting position, add initial plies */
7658 if( count == backwardMostMove )
7659 count -= initialRulePlies;
7660 count = forwardMostMove - count;
7661 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7662 // adjust reversible move counter for checks in Xiangqi
7663 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7664 if(i < backwardMostMove) i = backwardMostMove;
7665 while(i <= forwardMostMove) {
7666 lastCheck = inCheck; // check evasion does not count
7667 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7668 if(inCheck || lastCheck) count--; // check does not count
7673 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7674 /* this is used to judge if draw claims are legal */
7675 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7676 if(engineOpponent) {
7677 SendToProgram("force\n", engineOpponent); // suppress reply
7678 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7684 /* if draw offer is pending, treat it as a draw claim
7685 * when draw condition present, to allow engines a way to
7686 * claim draws before making their move to avoid a race
7687 * condition occurring after their move
7689 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7691 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7692 p = "Draw claim: 50-move rule";
7693 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7694 p = "Draw claim: 3-fold repetition";
7695 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7696 p = "Draw claim: insufficient mating material";
7697 if( p != NULL && canAdjudicate) {
7698 if(engineOpponent) {
7699 SendToProgram("force\n", engineOpponent); // suppress reply
7700 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702 GameEnds( GameIsDrawn, p, GE_XBOARD );
7707 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7708 if(engineOpponent) {
7709 SendToProgram("force\n", engineOpponent); // suppress reply
7710 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7712 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7718 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7719 { // [HGM] book: this routine intercepts moves to simulate book replies
7720 char *bookHit = NULL;
7722 //first determine if the incoming move brings opponent into his book
7723 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7724 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7725 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7726 if(bookHit != NULL && !cps->bookSuspend) {
7727 // make sure opponent is not going to reply after receiving move to book position
7728 SendToProgram("force\n", cps);
7729 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7731 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7732 // now arrange restart after book miss
7734 // after a book hit we never send 'go', and the code after the call to this routine
7735 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7736 char buf[MSG_SIZ], *move = bookHit;
7738 int fromX, fromY, toX, toY;
7742 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7743 &fromX, &fromY, &toX, &toY, &promoChar)) {
7744 (void) CoordsToAlgebraic(boards[forwardMostMove],
7745 PosFlags(forwardMostMove),
7746 fromY, fromX, toY, toX, promoChar, move);
7748 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7752 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7753 SendToProgram(buf, cps);
7754 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7755 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7756 SendToProgram("go\n", cps);
7757 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7758 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7759 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7760 SendToProgram("go\n", cps);
7761 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7763 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7767 ChessProgramState *savedState;
7768 void DeferredBookMove(void)
7770 if(savedState->lastPing != savedState->lastPong)
7771 ScheduleDelayedEvent(DeferredBookMove, 10);
7773 HandleMachineMove(savedMessage, savedState);
7776 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7779 HandleMachineMove(message, cps)
7781 ChessProgramState *cps;
7783 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7784 char realname[MSG_SIZ];
7785 int fromX, fromY, toX, toY;
7792 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7793 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7794 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7795 DisplayError(_("Invalid pairing from pairing engine"), 0);
7798 pairingReceived = 1;
7800 return; // Skim the pairing messages here.
7805 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7807 * Kludge to ignore BEL characters
7809 while (*message == '\007') message++;
7812 * [HGM] engine debug message: ignore lines starting with '#' character
7814 if(cps->debug && *message == '#') return;
7817 * Look for book output
7819 if (cps == &first && bookRequested) {
7820 if (message[0] == '\t' || message[0] == ' ') {
7821 /* Part of the book output is here; append it */
7822 strcat(bookOutput, message);
7823 strcat(bookOutput, " \n");
7825 } else if (bookOutput[0] != NULLCHAR) {
7826 /* All of book output has arrived; display it */
7827 char *p = bookOutput;
7828 while (*p != NULLCHAR) {
7829 if (*p == '\t') *p = ' ';
7832 DisplayInformation(bookOutput);
7833 bookRequested = FALSE;
7834 /* Fall through to parse the current output */
7839 * Look for machine move.
7841 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7842 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7844 /* This method is only useful on engines that support ping */
7845 if (cps->lastPing != cps->lastPong) {
7846 if (gameMode == BeginningOfGame) {
7847 /* Extra move from before last new; ignore */
7848 if (appData.debugMode) {
7849 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7852 if (appData.debugMode) {
7853 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7854 cps->which, gameMode);
7857 SendToProgram("undo\n", cps);
7863 case BeginningOfGame:
7864 /* Extra move from before last reset; ignore */
7865 if (appData.debugMode) {
7866 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7873 /* Extra move after we tried to stop. The mode test is
7874 not a reliable way of detecting this problem, but it's
7875 the best we can do on engines that don't support ping.
7877 if (appData.debugMode) {
7878 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7879 cps->which, gameMode);
7881 SendToProgram("undo\n", cps);
7884 case MachinePlaysWhite:
7885 case IcsPlayingWhite:
7886 machineWhite = TRUE;
7889 case MachinePlaysBlack:
7890 case IcsPlayingBlack:
7891 machineWhite = FALSE;
7894 case TwoMachinesPlay:
7895 machineWhite = (cps->twoMachinesColor[0] == 'w');
7898 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7899 if (appData.debugMode) {
7901 "Ignoring move out of turn by %s, gameMode %d"
7902 ", forwardMost %d\n",
7903 cps->which, gameMode, forwardMostMove);
7908 if (appData.debugMode) { int f = forwardMostMove;
7909 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7910 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7911 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7913 if(cps->alphaRank) AlphaRank(machineMove, 4);
7914 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7915 &fromX, &fromY, &toX, &toY, &promoChar)) {
7916 /* Machine move could not be parsed; ignore it. */
7917 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7918 machineMove, _(cps->which));
7919 DisplayError(buf1, 0);
7920 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7921 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7922 if (gameMode == TwoMachinesPlay) {
7923 GameEnds(machineWhite ? BlackWins : WhiteWins,
7929 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7930 /* So we have to redo legality test with true e.p. status here, */
7931 /* to make sure an illegal e.p. capture does not slip through, */
7932 /* to cause a forfeit on a justified illegal-move complaint */
7933 /* of the opponent. */
7934 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7936 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7937 fromY, fromX, toY, toX, promoChar);
7938 if (appData.debugMode) {
7940 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7941 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7942 fprintf(debugFP, "castling rights\n");
7944 if(moveType == IllegalMove) {
7945 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7946 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7947 GameEnds(machineWhite ? BlackWins : WhiteWins,
7950 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7951 /* [HGM] Kludge to handle engines that send FRC-style castling
7952 when they shouldn't (like TSCP-Gothic) */
7954 case WhiteASideCastleFR:
7955 case BlackASideCastleFR:
7957 currentMoveString[2]++;
7959 case WhiteHSideCastleFR:
7960 case BlackHSideCastleFR:
7962 currentMoveString[2]--;
7964 default: ; // nothing to do, but suppresses warning of pedantic compilers
7967 hintRequested = FALSE;
7968 lastHint[0] = NULLCHAR;
7969 bookRequested = FALSE;
7970 /* Program may be pondering now */
7971 cps->maybeThinking = TRUE;
7972 if (cps->sendTime == 2) cps->sendTime = 1;
7973 if (cps->offeredDraw) cps->offeredDraw--;
7975 /* [AS] Save move info*/
7976 pvInfoList[ forwardMostMove ].score = programStats.score;
7977 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7978 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7980 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7982 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7983 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7986 while( count < adjudicateLossPlies ) {
7987 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7990 score = -score; /* Flip score for winning side */
7993 if( score > adjudicateLossThreshold ) {
8000 if( count >= adjudicateLossPlies ) {
8001 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8003 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8004 "Xboard adjudication",
8011 if(Adjudicate(cps)) {
8012 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8013 return; // [HGM] adjudicate: for all automatic game ends
8017 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8019 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8020 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8022 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8026 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8027 char buf[3*MSG_SIZ];
8029 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8030 programStats.score / 100.,
8032 programStats.time / 100.,
8033 (unsigned int)programStats.nodes,
8034 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8035 programStats.movelist);
8037 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8042 /* [AS] Clear stats for next move */
8043 ClearProgramStats();
8044 thinkOutput[0] = NULLCHAR;
8045 hiddenThinkOutputState = 0;
8048 if (gameMode == TwoMachinesPlay) {
8049 /* [HGM] relaying draw offers moved to after reception of move */
8050 /* and interpreting offer as claim if it brings draw condition */
8051 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8052 SendToProgram("draw\n", cps->other);
8054 if (cps->other->sendTime) {
8055 SendTimeRemaining(cps->other,
8056 cps->other->twoMachinesColor[0] == 'w');
8058 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8059 if (firstMove && !bookHit) {
8061 if (cps->other->useColors) {
8062 SendToProgram(cps->other->twoMachinesColor, cps->other);
8064 SendToProgram("go\n", cps->other);
8066 cps->other->maybeThinking = TRUE;
8069 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8071 if (!pausing && appData.ringBellAfterMoves) {
8076 * Reenable menu items that were disabled while
8077 * machine was thinking
8079 if (gameMode != TwoMachinesPlay)
8080 SetUserThinkingEnables();
8082 // [HGM] book: after book hit opponent has received move and is now in force mode
8083 // force the book reply into it, and then fake that it outputted this move by jumping
8084 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8086 static char bookMove[MSG_SIZ]; // a bit generous?
8088 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8089 strcat(bookMove, bookHit);
8092 programStats.nodes = programStats.depth = programStats.time =
8093 programStats.score = programStats.got_only_move = 0;
8094 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8096 if(cps->lastPing != cps->lastPong) {
8097 savedMessage = message; // args for deferred call
8099 ScheduleDelayedEvent(DeferredBookMove, 10);
8108 /* Set special modes for chess engines. Later something general
8109 * could be added here; for now there is just one kludge feature,
8110 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8111 * when "xboard" is given as an interactive command.
8113 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8114 cps->useSigint = FALSE;
8115 cps->useSigterm = FALSE;
8117 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8118 ParseFeatures(message+8, cps);
8119 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8122 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8123 int dummy, s=6; char buf[MSG_SIZ];
8124 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8125 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8126 ParseFEN(boards[0], &dummy, message+s);
8127 DrawPosition(TRUE, boards[0]);
8128 startedFromSetupPosition = TRUE;
8131 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8132 * want this, I was asked to put it in, and obliged.
8134 if (!strncmp(message, "setboard ", 9)) {
8135 Board initial_position;
8137 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8139 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8140 DisplayError(_("Bad FEN received from engine"), 0);
8144 CopyBoard(boards[0], initial_position);
8145 initialRulePlies = FENrulePlies;
8146 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8147 else gameMode = MachinePlaysBlack;
8148 DrawPosition(FALSE, boards[currentMove]);
8154 * Look for communication commands
8156 if (!strncmp(message, "telluser ", 9)) {
8157 if(message[9] == '\\' && message[10] == '\\')
8158 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8160 DisplayNote(message + 9);
8163 if (!strncmp(message, "tellusererror ", 14)) {
8165 if(message[14] == '\\' && message[15] == '\\')
8166 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8168 DisplayError(message + 14, 0);
8171 if (!strncmp(message, "tellopponent ", 13)) {
8172 if (appData.icsActive) {
8174 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8178 DisplayNote(message + 13);
8182 if (!strncmp(message, "tellothers ", 11)) {
8183 if (appData.icsActive) {
8185 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8191 if (!strncmp(message, "tellall ", 8)) {
8192 if (appData.icsActive) {
8194 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8198 DisplayNote(message + 8);
8202 if (strncmp(message, "warning", 7) == 0) {
8203 /* Undocumented feature, use tellusererror in new code */
8204 DisplayError(message, 0);
8207 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8208 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8209 strcat(realname, " query");
8210 AskQuestion(realname, buf2, buf1, cps->pr);
8213 /* Commands from the engine directly to ICS. We don't allow these to be
8214 * sent until we are logged on. Crafty kibitzes have been known to
8215 * interfere with the login process.
8218 if (!strncmp(message, "tellics ", 8)) {
8219 SendToICS(message + 8);
8223 if (!strncmp(message, "tellicsnoalias ", 15)) {
8224 SendToICS(ics_prefix);
8225 SendToICS(message + 15);
8229 /* The following are for backward compatibility only */
8230 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8231 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8232 SendToICS(ics_prefix);
8238 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8242 * If the move is illegal, cancel it and redraw the board.
8243 * Also deal with other error cases. Matching is rather loose
8244 * here to accommodate engines written before the spec.
8246 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8247 strncmp(message, "Error", 5) == 0) {
8248 if (StrStr(message, "name") ||
8249 StrStr(message, "rating") || StrStr(message, "?") ||
8250 StrStr(message, "result") || StrStr(message, "board") ||
8251 StrStr(message, "bk") || StrStr(message, "computer") ||
8252 StrStr(message, "variant") || StrStr(message, "hint") ||
8253 StrStr(message, "random") || StrStr(message, "depth") ||
8254 StrStr(message, "accepted")) {
8257 if (StrStr(message, "protover")) {
8258 /* Program is responding to input, so it's apparently done
8259 initializing, and this error message indicates it is
8260 protocol version 1. So we don't need to wait any longer
8261 for it to initialize and send feature commands. */
8262 FeatureDone(cps, 1);
8263 cps->protocolVersion = 1;
8266 cps->maybeThinking = FALSE;
8268 if (StrStr(message, "draw")) {
8269 /* Program doesn't have "draw" command */
8270 cps->sendDrawOffers = 0;
8273 if (cps->sendTime != 1 &&
8274 (StrStr(message, "time") || StrStr(message, "otim"))) {
8275 /* Program apparently doesn't have "time" or "otim" command */
8279 if (StrStr(message, "analyze")) {
8280 cps->analysisSupport = FALSE;
8281 cps->analyzing = FALSE;
8282 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8283 EditGameEvent(); // [HGM] try to preserve loaded game
8284 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8285 DisplayError(buf2, 0);
8288 if (StrStr(message, "(no matching move)st")) {
8289 /* Special kludge for GNU Chess 4 only */
8290 cps->stKludge = TRUE;
8291 SendTimeControl(cps, movesPerSession, timeControl,
8292 timeIncrement, appData.searchDepth,
8296 if (StrStr(message, "(no matching move)sd")) {
8297 /* Special kludge for GNU Chess 4 only */
8298 cps->sdKludge = TRUE;
8299 SendTimeControl(cps, movesPerSession, timeControl,
8300 timeIncrement, appData.searchDepth,
8304 if (!StrStr(message, "llegal")) {
8307 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8308 gameMode == IcsIdle) return;
8309 if (forwardMostMove <= backwardMostMove) return;
8310 if (pausing) PauseEvent();
8311 if(appData.forceIllegal) {
8312 // [HGM] illegal: machine refused move; force position after move into it
8313 SendToProgram("force\n", cps);
8314 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8315 // we have a real problem now, as SendBoard will use the a2a3 kludge
8316 // when black is to move, while there might be nothing on a2 or black
8317 // might already have the move. So send the board as if white has the move.
8318 // But first we must change the stm of the engine, as it refused the last move
8319 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8320 if(WhiteOnMove(forwardMostMove)) {
8321 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8322 SendBoard(cps, forwardMostMove); // kludgeless board
8324 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8325 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8326 SendBoard(cps, forwardMostMove+1); // kludgeless board
8328 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8329 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8330 gameMode == TwoMachinesPlay)
8331 SendToProgram("go\n", cps);
8334 if (gameMode == PlayFromGameFile) {
8335 /* Stop reading this game file */
8336 gameMode = EditGame;
8339 /* [HGM] illegal-move claim should forfeit game when Xboard */
8340 /* only passes fully legal moves */
8341 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8342 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8343 "False illegal-move claim", GE_XBOARD );
8344 return; // do not take back move we tested as valid
8346 currentMove = forwardMostMove-1;
8347 DisplayMove(currentMove-1); /* before DisplayMoveError */
8348 SwitchClocks(forwardMostMove-1); // [HGM] race
8349 DisplayBothClocks();
8350 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8351 parseList[currentMove], _(cps->which));
8352 DisplayMoveError(buf1);
8353 DrawPosition(FALSE, boards[currentMove]);
8356 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8357 /* Program has a broken "time" command that
8358 outputs a string not ending in newline.
8364 * If chess program startup fails, exit with an error message.
8365 * Attempts to recover here are futile.
8367 if ((StrStr(message, "unknown host") != NULL)
8368 || (StrStr(message, "No remote directory") != NULL)
8369 || (StrStr(message, "not found") != NULL)
8370 || (StrStr(message, "No such file") != NULL)
8371 || (StrStr(message, "can't alloc") != NULL)
8372 || (StrStr(message, "Permission denied") != NULL)) {
8374 cps->maybeThinking = FALSE;
8375 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8376 _(cps->which), cps->program, cps->host, message);
8377 RemoveInputSource(cps->isr);
8378 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8379 if(cps == &first) appData.noChessProgram = TRUE;
8380 DisplayError(buf1, 0);
8386 * Look for hint output
8388 if (sscanf(message, "Hint: %s", buf1) == 1) {
8389 if (cps == &first && hintRequested) {
8390 hintRequested = FALSE;
8391 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8392 &fromX, &fromY, &toX, &toY, &promoChar)) {
8393 (void) CoordsToAlgebraic(boards[forwardMostMove],
8394 PosFlags(forwardMostMove),
8395 fromY, fromX, toY, toX, promoChar, buf1);
8396 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8397 DisplayInformation(buf2);
8399 /* Hint move could not be parsed!? */
8400 snprintf(buf2, sizeof(buf2),
8401 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8402 buf1, _(cps->which));
8403 DisplayError(buf2, 0);
8406 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8412 * Ignore other messages if game is not in progress
8414 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8415 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8418 * look for win, lose, draw, or draw offer
8420 if (strncmp(message, "1-0", 3) == 0) {
8421 char *p, *q, *r = "";
8422 p = strchr(message, '{');
8430 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8432 } else if (strncmp(message, "0-1", 3) == 0) {
8433 char *p, *q, *r = "";
8434 p = strchr(message, '{');
8442 /* Kludge for Arasan 4.1 bug */
8443 if (strcmp(r, "Black resigns") == 0) {
8444 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8447 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8449 } else if (strncmp(message, "1/2", 3) == 0) {
8450 char *p, *q, *r = "";
8451 p = strchr(message, '{');
8460 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8463 } else if (strncmp(message, "White resign", 12) == 0) {
8464 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8466 } else if (strncmp(message, "Black resign", 12) == 0) {
8467 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8469 } else if (strncmp(message, "White matches", 13) == 0 ||
8470 strncmp(message, "Black matches", 13) == 0 ) {
8471 /* [HGM] ignore GNUShogi noises */
8473 } else if (strncmp(message, "White", 5) == 0 &&
8474 message[5] != '(' &&
8475 StrStr(message, "Black") == NULL) {
8476 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8478 } else if (strncmp(message, "Black", 5) == 0 &&
8479 message[5] != '(') {
8480 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8482 } else if (strcmp(message, "resign") == 0 ||
8483 strcmp(message, "computer resigns") == 0) {
8485 case MachinePlaysBlack:
8486 case IcsPlayingBlack:
8487 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8489 case MachinePlaysWhite:
8490 case IcsPlayingWhite:
8491 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8493 case TwoMachinesPlay:
8494 if (cps->twoMachinesColor[0] == 'w')
8495 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8497 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8504 } else if (strncmp(message, "opponent mates", 14) == 0) {
8506 case MachinePlaysBlack:
8507 case IcsPlayingBlack:
8508 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510 case MachinePlaysWhite:
8511 case IcsPlayingWhite:
8512 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8514 case TwoMachinesPlay:
8515 if (cps->twoMachinesColor[0] == 'w')
8516 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8518 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8525 } else if (strncmp(message, "computer mates", 14) == 0) {
8527 case MachinePlaysBlack:
8528 case IcsPlayingBlack:
8529 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8531 case MachinePlaysWhite:
8532 case IcsPlayingWhite:
8533 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8535 case TwoMachinesPlay:
8536 if (cps->twoMachinesColor[0] == 'w')
8537 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8546 } else if (strncmp(message, "checkmate", 9) == 0) {
8547 if (WhiteOnMove(forwardMostMove)) {
8548 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8550 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8553 } else if (strstr(message, "Draw") != NULL ||
8554 strstr(message, "game is a draw") != NULL) {
8555 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8557 } else if (strstr(message, "offer") != NULL &&
8558 strstr(message, "draw") != NULL) {
8560 if (appData.zippyPlay && first.initDone) {
8561 /* Relay offer to ICS */
8562 SendToICS(ics_prefix);
8563 SendToICS("draw\n");
8566 cps->offeredDraw = 2; /* valid until this engine moves twice */
8567 if (gameMode == TwoMachinesPlay) {
8568 if (cps->other->offeredDraw) {
8569 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8570 /* [HGM] in two-machine mode we delay relaying draw offer */
8571 /* until after we also have move, to see if it is really claim */
8573 } else if (gameMode == MachinePlaysWhite ||
8574 gameMode == MachinePlaysBlack) {
8575 if (userOfferedDraw) {
8576 DisplayInformation(_("Machine accepts your draw offer"));
8577 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8586 * Look for thinking output
8588 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8589 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8591 int plylev, mvleft, mvtot, curscore, time;
8592 char mvname[MOVE_LEN];
8596 int prefixHint = FALSE;
8597 mvname[0] = NULLCHAR;
8600 case MachinePlaysBlack:
8601 case IcsPlayingBlack:
8602 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8604 case MachinePlaysWhite:
8605 case IcsPlayingWhite:
8606 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8611 case IcsObserving: /* [DM] icsEngineAnalyze */
8612 if (!appData.icsEngineAnalyze) ignore = TRUE;
8614 case TwoMachinesPlay:
8615 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8625 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8627 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8628 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8630 if (plyext != ' ' && plyext != '\t') {
8634 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8635 if( cps->scoreIsAbsolute &&
8636 ( gameMode == MachinePlaysBlack ||
8637 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8638 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8639 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8640 !WhiteOnMove(currentMove)
8643 curscore = -curscore;
8646 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8648 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8651 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8652 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8653 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8654 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8655 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8656 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8658 } else DisplayError("failed writing PV", 0);
8661 tempStats.depth = plylev;
8662 tempStats.nodes = nodes;
8663 tempStats.time = time;
8664 tempStats.score = curscore;
8665 tempStats.got_only_move = 0;
8667 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8670 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8671 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8672 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8673 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8674 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8675 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8676 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8677 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8680 /* Buffer overflow protection */
8681 if (pv[0] != NULLCHAR) {
8682 if (strlen(pv) >= sizeof(tempStats.movelist)
8683 && appData.debugMode) {
8685 "PV is too long; using the first %u bytes.\n",
8686 (unsigned) sizeof(tempStats.movelist) - 1);
8689 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8691 sprintf(tempStats.movelist, " no PV\n");
8694 if (tempStats.seen_stat) {
8695 tempStats.ok_to_send = 1;
8698 if (strchr(tempStats.movelist, '(') != NULL) {
8699 tempStats.line_is_book = 1;
8700 tempStats.nr_moves = 0;
8701 tempStats.moves_left = 0;
8703 tempStats.line_is_book = 0;
8706 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8707 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8709 SendProgramStatsToFrontend( cps, &tempStats );
8712 [AS] Protect the thinkOutput buffer from overflow... this
8713 is only useful if buf1 hasn't overflowed first!
8715 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8717 (gameMode == TwoMachinesPlay ?
8718 ToUpper(cps->twoMachinesColor[0]) : ' '),
8719 ((double) curscore) / 100.0,
8720 prefixHint ? lastHint : "",
8721 prefixHint ? " " : "" );
8723 if( buf1[0] != NULLCHAR ) {
8724 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8726 if( strlen(pv) > max_len ) {
8727 if( appData.debugMode) {
8728 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8730 pv[max_len+1] = '\0';
8733 strcat( thinkOutput, pv);
8736 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8737 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8738 DisplayMove(currentMove - 1);
8742 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8743 /* crafty (9.25+) says "(only move) <move>"
8744 * if there is only 1 legal move
8746 sscanf(p, "(only move) %s", buf1);
8747 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8748 sprintf(programStats.movelist, "%s (only move)", buf1);
8749 programStats.depth = 1;
8750 programStats.nr_moves = 1;
8751 programStats.moves_left = 1;
8752 programStats.nodes = 1;
8753 programStats.time = 1;
8754 programStats.got_only_move = 1;
8756 /* Not really, but we also use this member to
8757 mean "line isn't going to change" (Crafty
8758 isn't searching, so stats won't change) */
8759 programStats.line_is_book = 1;
8761 SendProgramStatsToFrontend( cps, &programStats );
8763 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8764 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8765 DisplayMove(currentMove - 1);
8768 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8769 &time, &nodes, &plylev, &mvleft,
8770 &mvtot, mvname) >= 5) {
8771 /* The stat01: line is from Crafty (9.29+) in response
8772 to the "." command */
8773 programStats.seen_stat = 1;
8774 cps->maybeThinking = TRUE;
8776 if (programStats.got_only_move || !appData.periodicUpdates)
8779 programStats.depth = plylev;
8780 programStats.time = time;
8781 programStats.nodes = nodes;
8782 programStats.moves_left = mvleft;
8783 programStats.nr_moves = mvtot;
8784 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8785 programStats.ok_to_send = 1;
8786 programStats.movelist[0] = '\0';
8788 SendProgramStatsToFrontend( cps, &programStats );
8792 } else if (strncmp(message,"++",2) == 0) {
8793 /* Crafty 9.29+ outputs this */
8794 programStats.got_fail = 2;
8797 } else if (strncmp(message,"--",2) == 0) {
8798 /* Crafty 9.29+ outputs this */
8799 programStats.got_fail = 1;
8802 } else if (thinkOutput[0] != NULLCHAR &&
8803 strncmp(message, " ", 4) == 0) {
8804 unsigned message_len;
8807 while (*p && *p == ' ') p++;
8809 message_len = strlen( p );
8811 /* [AS] Avoid buffer overflow */
8812 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8813 strcat(thinkOutput, " ");
8814 strcat(thinkOutput, p);
8817 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8818 strcat(programStats.movelist, " ");
8819 strcat(programStats.movelist, p);
8822 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8823 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8824 DisplayMove(currentMove - 1);
8832 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8833 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8835 ChessProgramStats cpstats;
8837 if (plyext != ' ' && plyext != '\t') {
8841 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8842 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8843 curscore = -curscore;
8846 cpstats.depth = plylev;
8847 cpstats.nodes = nodes;
8848 cpstats.time = time;
8849 cpstats.score = curscore;
8850 cpstats.got_only_move = 0;
8851 cpstats.movelist[0] = '\0';
8853 if (buf1[0] != NULLCHAR) {
8854 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8857 cpstats.ok_to_send = 0;
8858 cpstats.line_is_book = 0;
8859 cpstats.nr_moves = 0;
8860 cpstats.moves_left = 0;
8862 SendProgramStatsToFrontend( cps, &cpstats );
8869 /* Parse a game score from the character string "game", and
8870 record it as the history of the current game. The game
8871 score is NOT assumed to start from the standard position.
8872 The display is not updated in any way.
8875 ParseGameHistory(game)
8879 int fromX, fromY, toX, toY, boardIndex;
8884 if (appData.debugMode)
8885 fprintf(debugFP, "Parsing game history: %s\n", game);
8887 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8888 gameInfo.site = StrSave(appData.icsHost);
8889 gameInfo.date = PGNDate();
8890 gameInfo.round = StrSave("-");
8892 /* Parse out names of players */
8893 while (*game == ' ') game++;
8895 while (*game != ' ') *p++ = *game++;
8897 gameInfo.white = StrSave(buf);
8898 while (*game == ' ') game++;
8900 while (*game != ' ' && *game != '\n') *p++ = *game++;
8902 gameInfo.black = StrSave(buf);
8905 boardIndex = blackPlaysFirst ? 1 : 0;
8908 yyboardindex = boardIndex;
8909 moveType = (ChessMove) Myylex();
8911 case IllegalMove: /* maybe suicide chess, etc. */
8912 if (appData.debugMode) {
8913 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8914 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8915 setbuf(debugFP, NULL);
8917 case WhitePromotion:
8918 case BlackPromotion:
8919 case WhiteNonPromotion:
8920 case BlackNonPromotion:
8922 case WhiteCapturesEnPassant:
8923 case BlackCapturesEnPassant:
8924 case WhiteKingSideCastle:
8925 case WhiteQueenSideCastle:
8926 case BlackKingSideCastle:
8927 case BlackQueenSideCastle:
8928 case WhiteKingSideCastleWild:
8929 case WhiteQueenSideCastleWild:
8930 case BlackKingSideCastleWild:
8931 case BlackQueenSideCastleWild:
8933 case WhiteHSideCastleFR:
8934 case WhiteASideCastleFR:
8935 case BlackHSideCastleFR:
8936 case BlackASideCastleFR:
8938 fromX = currentMoveString[0] - AAA;
8939 fromY = currentMoveString[1] - ONE;
8940 toX = currentMoveString[2] - AAA;
8941 toY = currentMoveString[3] - ONE;
8942 promoChar = currentMoveString[4];
8946 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8947 fromX = moveType == WhiteDrop ?
8948 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8949 (int) CharToPiece(ToLower(currentMoveString[0]));
8951 toX = currentMoveString[2] - AAA;
8952 toY = currentMoveString[3] - ONE;
8953 promoChar = NULLCHAR;
8957 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8958 if (appData.debugMode) {
8959 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8960 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8961 setbuf(debugFP, NULL);
8963 DisplayError(buf, 0);
8965 case ImpossibleMove:
8967 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8968 if (appData.debugMode) {
8969 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8970 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8971 setbuf(debugFP, NULL);
8973 DisplayError(buf, 0);
8976 if (boardIndex < backwardMostMove) {
8977 /* Oops, gap. How did that happen? */
8978 DisplayError(_("Gap in move list"), 0);
8981 backwardMostMove = blackPlaysFirst ? 1 : 0;
8982 if (boardIndex > forwardMostMove) {
8983 forwardMostMove = boardIndex;
8987 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8988 strcat(parseList[boardIndex-1], " ");
8989 strcat(parseList[boardIndex-1], yy_text);
9001 case GameUnfinished:
9002 if (gameMode == IcsExamining) {
9003 if (boardIndex < backwardMostMove) {
9004 /* Oops, gap. How did that happen? */
9007 backwardMostMove = blackPlaysFirst ? 1 : 0;
9010 gameInfo.result = moveType;
9011 p = strchr(yy_text, '{');
9012 if (p == NULL) p = strchr(yy_text, '(');
9015 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9017 q = strchr(p, *p == '{' ? '}' : ')');
9018 if (q != NULL) *q = NULLCHAR;
9021 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9022 gameInfo.resultDetails = StrSave(p);
9025 if (boardIndex >= forwardMostMove &&
9026 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9027 backwardMostMove = blackPlaysFirst ? 1 : 0;
9030 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9031 fromY, fromX, toY, toX, promoChar,
9032 parseList[boardIndex]);
9033 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9034 /* currentMoveString is set as a side-effect of yylex */
9035 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9036 strcat(moveList[boardIndex], "\n");
9038 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9039 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9045 if(gameInfo.variant != VariantShogi)
9046 strcat(parseList[boardIndex - 1], "+");
9050 strcat(parseList[boardIndex - 1], "#");
9057 /* Apply a move to the given board */
9059 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9060 int fromX, fromY, toX, toY;
9064 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9065 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9067 /* [HGM] compute & store e.p. status and castling rights for new position */
9068 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9070 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9071 oldEP = (signed char)board[EP_STATUS];
9072 board[EP_STATUS] = EP_NONE;
9074 if (fromY == DROP_RANK) {
9076 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9077 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9080 piece = board[toY][toX] = (ChessSquare) fromX;
9084 if( board[toY][toX] != EmptySquare )
9085 board[EP_STATUS] = EP_CAPTURE;
9087 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9088 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9089 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9091 if( board[fromY][fromX] == WhitePawn ) {
9092 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9093 board[EP_STATUS] = EP_PAWN_MOVE;
9095 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9096 gameInfo.variant != VariantBerolina || toX < fromX)
9097 board[EP_STATUS] = toX | berolina;
9098 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9099 gameInfo.variant != VariantBerolina || toX > fromX)
9100 board[EP_STATUS] = toX;
9103 if( board[fromY][fromX] == BlackPawn ) {
9104 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9105 board[EP_STATUS] = EP_PAWN_MOVE;
9106 if( toY-fromY== -2) {
9107 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9108 gameInfo.variant != VariantBerolina || toX < fromX)
9109 board[EP_STATUS] = toX | berolina;
9110 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9111 gameInfo.variant != VariantBerolina || toX > fromX)
9112 board[EP_STATUS] = toX;
9116 for(i=0; i<nrCastlingRights; i++) {
9117 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9118 board[CASTLING][i] == toX && castlingRank[i] == toY
9119 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9122 if (fromX == toX && fromY == toY) return;
9124 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9125 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9126 if(gameInfo.variant == VariantKnightmate)
9127 king += (int) WhiteUnicorn - (int) WhiteKing;
9129 /* Code added by Tord: */
9130 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9131 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9132 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9133 board[fromY][fromX] = EmptySquare;
9134 board[toY][toX] = EmptySquare;
9135 if((toX > fromX) != (piece == WhiteRook)) {
9136 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9138 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9140 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9141 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9142 board[fromY][fromX] = EmptySquare;
9143 board[toY][toX] = EmptySquare;
9144 if((toX > fromX) != (piece == BlackRook)) {
9145 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9147 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9149 /* End of code added by Tord */
9151 } else if (board[fromY][fromX] == king
9152 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9153 && toY == fromY && toX > fromX+1) {
9154 board[fromY][fromX] = EmptySquare;
9155 board[toY][toX] = king;
9156 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9157 board[fromY][BOARD_RGHT-1] = EmptySquare;
9158 } else if (board[fromY][fromX] == king
9159 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9160 && toY == fromY && toX < fromX-1) {
9161 board[fromY][fromX] = EmptySquare;
9162 board[toY][toX] = king;
9163 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9164 board[fromY][BOARD_LEFT] = EmptySquare;
9165 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9166 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9167 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9169 /* white pawn promotion */
9170 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9171 if(gameInfo.variant==VariantBughouse ||
9172 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9173 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9174 board[fromY][fromX] = EmptySquare;
9175 } else if ((fromY >= BOARD_HEIGHT>>1)
9177 && gameInfo.variant != VariantXiangqi
9178 && gameInfo.variant != VariantBerolina
9179 && (board[fromY][fromX] == WhitePawn)
9180 && (board[toY][toX] == EmptySquare)) {
9181 board[fromY][fromX] = EmptySquare;
9182 board[toY][toX] = WhitePawn;
9183 captured = board[toY - 1][toX];
9184 board[toY - 1][toX] = EmptySquare;
9185 } else if ((fromY == BOARD_HEIGHT-4)
9187 && gameInfo.variant == VariantBerolina
9188 && (board[fromY][fromX] == WhitePawn)
9189 && (board[toY][toX] == EmptySquare)) {
9190 board[fromY][fromX] = EmptySquare;
9191 board[toY][toX] = WhitePawn;
9192 if(oldEP & EP_BEROLIN_A) {
9193 captured = board[fromY][fromX-1];
9194 board[fromY][fromX-1] = EmptySquare;
9195 }else{ captured = board[fromY][fromX+1];
9196 board[fromY][fromX+1] = EmptySquare;
9198 } else if (board[fromY][fromX] == king
9199 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9200 && toY == fromY && toX > fromX+1) {
9201 board[fromY][fromX] = EmptySquare;
9202 board[toY][toX] = king;
9203 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9204 board[fromY][BOARD_RGHT-1] = EmptySquare;
9205 } else if (board[fromY][fromX] == king
9206 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9207 && toY == fromY && toX < fromX-1) {
9208 board[fromY][fromX] = EmptySquare;
9209 board[toY][toX] = king;
9210 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9211 board[fromY][BOARD_LEFT] = EmptySquare;
9212 } else if (fromY == 7 && fromX == 3
9213 && board[fromY][fromX] == BlackKing
9214 && toY == 7 && toX == 5) {
9215 board[fromY][fromX] = EmptySquare;
9216 board[toY][toX] = BlackKing;
9217 board[fromY][7] = EmptySquare;
9218 board[toY][4] = BlackRook;
9219 } else if (fromY == 7 && fromX == 3
9220 && board[fromY][fromX] == BlackKing
9221 && toY == 7 && toX == 1) {
9222 board[fromY][fromX] = EmptySquare;
9223 board[toY][toX] = BlackKing;
9224 board[fromY][0] = EmptySquare;
9225 board[toY][2] = BlackRook;
9226 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9227 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9228 && toY < promoRank && promoChar
9230 /* black pawn promotion */
9231 board[toY][toX] = CharToPiece(ToLower(promoChar));
9232 if(gameInfo.variant==VariantBughouse ||
9233 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9234 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9235 board[fromY][fromX] = EmptySquare;
9236 } else if ((fromY < BOARD_HEIGHT>>1)
9238 && gameInfo.variant != VariantXiangqi
9239 && gameInfo.variant != VariantBerolina
9240 && (board[fromY][fromX] == BlackPawn)
9241 && (board[toY][toX] == EmptySquare)) {
9242 board[fromY][fromX] = EmptySquare;
9243 board[toY][toX] = BlackPawn;
9244 captured = board[toY + 1][toX];
9245 board[toY + 1][toX] = EmptySquare;
9246 } else if ((fromY == 3)
9248 && gameInfo.variant == VariantBerolina
9249 && (board[fromY][fromX] == BlackPawn)
9250 && (board[toY][toX] == EmptySquare)) {
9251 board[fromY][fromX] = EmptySquare;
9252 board[toY][toX] = BlackPawn;
9253 if(oldEP & EP_BEROLIN_A) {
9254 captured = board[fromY][fromX-1];
9255 board[fromY][fromX-1] = EmptySquare;
9256 }else{ captured = board[fromY][fromX+1];
9257 board[fromY][fromX+1] = EmptySquare;
9260 board[toY][toX] = board[fromY][fromX];
9261 board[fromY][fromX] = EmptySquare;
9265 if (gameInfo.holdingsWidth != 0) {
9267 /* !!A lot more code needs to be written to support holdings */
9268 /* [HGM] OK, so I have written it. Holdings are stored in the */
9269 /* penultimate board files, so they are automaticlly stored */
9270 /* in the game history. */
9271 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9272 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9273 /* Delete from holdings, by decreasing count */
9274 /* and erasing image if necessary */
9275 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9276 if(p < (int) BlackPawn) { /* white drop */
9277 p -= (int)WhitePawn;
9278 p = PieceToNumber((ChessSquare)p);
9279 if(p >= gameInfo.holdingsSize) p = 0;
9280 if(--board[p][BOARD_WIDTH-2] <= 0)
9281 board[p][BOARD_WIDTH-1] = EmptySquare;
9282 if((int)board[p][BOARD_WIDTH-2] < 0)
9283 board[p][BOARD_WIDTH-2] = 0;
9284 } else { /* black drop */
9285 p -= (int)BlackPawn;
9286 p = PieceToNumber((ChessSquare)p);
9287 if(p >= gameInfo.holdingsSize) p = 0;
9288 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9289 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9290 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9291 board[BOARD_HEIGHT-1-p][1] = 0;
9294 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9295 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9296 /* [HGM] holdings: Add to holdings, if holdings exist */
9297 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9298 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9299 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9302 if (p >= (int) BlackPawn) {
9303 p -= (int)BlackPawn;
9304 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9305 /* in Shogi restore piece to its original first */
9306 captured = (ChessSquare) (DEMOTED captured);
9309 p = PieceToNumber((ChessSquare)p);
9310 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9311 board[p][BOARD_WIDTH-2]++;
9312 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9314 p -= (int)WhitePawn;
9315 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9316 captured = (ChessSquare) (DEMOTED captured);
9319 p = PieceToNumber((ChessSquare)p);
9320 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9321 board[BOARD_HEIGHT-1-p][1]++;
9322 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9325 } else if (gameInfo.variant == VariantAtomic) {
9326 if (captured != EmptySquare) {
9328 for (y = toY-1; y <= toY+1; y++) {
9329 for (x = toX-1; x <= toX+1; x++) {
9330 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9331 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9332 board[y][x] = EmptySquare;
9336 board[toY][toX] = EmptySquare;
9339 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9340 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9342 if(promoChar == '+') {
9343 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9344 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9345 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9346 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9348 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9349 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9350 // [HGM] superchess: take promotion piece out of holdings
9351 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9352 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9353 if(!--board[k][BOARD_WIDTH-2])
9354 board[k][BOARD_WIDTH-1] = EmptySquare;
9356 if(!--board[BOARD_HEIGHT-1-k][1])
9357 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9363 /* Updates forwardMostMove */
9365 MakeMove(fromX, fromY, toX, toY, promoChar)
9366 int fromX, fromY, toX, toY;
9369 // forwardMostMove++; // [HGM] bare: moved downstream
9371 (void) CoordsToAlgebraic(boards[forwardMostMove],
9372 PosFlags(forwardMostMove),
9373 fromY, fromX, toY, toX, promoChar,
9374 parseList[forwardMostMove]);
9376 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9377 int timeLeft; static int lastLoadFlag=0; int king, piece;
9378 piece = boards[forwardMostMove][fromY][fromX];
9379 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9380 if(gameInfo.variant == VariantKnightmate)
9381 king += (int) WhiteUnicorn - (int) WhiteKing;
9382 if(forwardMostMove == 0) {
9383 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9384 fprintf(serverMoves, "%s;", UserName());
9385 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9386 fprintf(serverMoves, "%s;", second.tidy);
9387 fprintf(serverMoves, "%s;", first.tidy);
9388 if(gameMode == MachinePlaysWhite)
9389 fprintf(serverMoves, "%s;", UserName());
9390 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9391 fprintf(serverMoves, "%s;", second.tidy);
9392 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9393 lastLoadFlag = loadFlag;
9395 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9396 // print castling suffix
9397 if( toY == fromY && piece == king ) {
9399 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9401 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9404 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9405 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9406 boards[forwardMostMove][toY][toX] == EmptySquare
9407 && fromX != toX && fromY != toY)
9408 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9410 if(promoChar != NULLCHAR)
9411 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9413 char buf[MOVE_LEN*2], *p; int len;
9414 fprintf(serverMoves, "/%d/%d",
9415 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9416 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9417 else timeLeft = blackTimeRemaining/1000;
9418 fprintf(serverMoves, "/%d", timeLeft);
9419 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9420 if(p = strchr(buf, '=')) *p = NULLCHAR;
9421 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9422 fprintf(serverMoves, "/%s", buf);
9424 fflush(serverMoves);
9427 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9428 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9431 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9432 if (commentList[forwardMostMove+1] != NULL) {
9433 free(commentList[forwardMostMove+1]);
9434 commentList[forwardMostMove+1] = NULL;
9436 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9437 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9438 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9439 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9440 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9441 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9442 adjustedClock = FALSE;
9443 gameInfo.result = GameUnfinished;
9444 if (gameInfo.resultDetails != NULL) {
9445 free(gameInfo.resultDetails);
9446 gameInfo.resultDetails = NULL;
9448 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9449 moveList[forwardMostMove - 1]);
9450 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9456 if(gameInfo.variant != VariantShogi)
9457 strcat(parseList[forwardMostMove - 1], "+");
9461 strcat(parseList[forwardMostMove - 1], "#");
9464 if (appData.debugMode) {
9465 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9470 /* Updates currentMove if not pausing */
9472 ShowMove(fromX, fromY, toX, toY)
9474 int instant = (gameMode == PlayFromGameFile) ?
9475 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9476 if(appData.noGUI) return;
9477 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9479 if (forwardMostMove == currentMove + 1) {
9480 AnimateMove(boards[forwardMostMove - 1],
9481 fromX, fromY, toX, toY);
9483 if (appData.highlightLastMove) {
9484 SetHighlights(fromX, fromY, toX, toY);
9487 currentMove = forwardMostMove;
9490 if (instant) return;
9492 DisplayMove(currentMove - 1);
9493 DrawPosition(FALSE, boards[currentMove]);
9494 DisplayBothClocks();
9495 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9498 void SendEgtPath(ChessProgramState *cps)
9499 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9500 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9502 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9505 char c, *q = name+1, *r, *s;
9507 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9508 while(*p && *p != ',') *q++ = *p++;
9510 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9511 strcmp(name, ",nalimov:") == 0 ) {
9512 // take nalimov path from the menu-changeable option first, if it is defined
9513 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9514 SendToProgram(buf,cps); // send egtbpath command for nalimov
9516 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9517 (s = StrStr(appData.egtFormats, name)) != NULL) {
9518 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9519 s = r = StrStr(s, ":") + 1; // beginning of path info
9520 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9521 c = *r; *r = 0; // temporarily null-terminate path info
9522 *--q = 0; // strip of trailig ':' from name
9523 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9525 SendToProgram(buf,cps); // send egtbpath command for this format
9527 if(*p == ',') p++; // read away comma to position for next format name
9532 InitChessProgram(cps, setup)
9533 ChessProgramState *cps;
9534 int setup; /* [HGM] needed to setup FRC opening position */
9536 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9537 if (appData.noChessProgram) return;
9538 hintRequested = FALSE;
9539 bookRequested = FALSE;
9541 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9542 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9543 if(cps->memSize) { /* [HGM] memory */
9544 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9545 SendToProgram(buf, cps);
9547 SendEgtPath(cps); /* [HGM] EGT */
9548 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9549 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9550 SendToProgram(buf, cps);
9553 SendToProgram(cps->initString, cps);
9554 if (gameInfo.variant != VariantNormal &&
9555 gameInfo.variant != VariantLoadable
9556 /* [HGM] also send variant if board size non-standard */
9557 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9559 char *v = VariantName(gameInfo.variant);
9560 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9561 /* [HGM] in protocol 1 we have to assume all variants valid */
9562 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9563 DisplayFatalError(buf, 0, 1);
9567 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9568 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9569 if( gameInfo.variant == VariantXiangqi )
9570 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9571 if( gameInfo.variant == VariantShogi )
9572 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9573 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9574 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9575 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9576 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9577 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578 if( gameInfo.variant == VariantCourier )
9579 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580 if( gameInfo.variant == VariantSuper )
9581 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582 if( gameInfo.variant == VariantGreat )
9583 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9584 if( gameInfo.variant == VariantSChess )
9585 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9586 if( gameInfo.variant == VariantGrand )
9587 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9590 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9591 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9592 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9593 if(StrStr(cps->variants, b) == NULL) {
9594 // specific sized variant not known, check if general sizing allowed
9595 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9596 if(StrStr(cps->variants, "boardsize") == NULL) {
9597 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9598 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9599 DisplayFatalError(buf, 0, 1);
9602 /* [HGM] here we really should compare with the maximum supported board size */
9605 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9606 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9607 SendToProgram(buf, cps);
9609 currentlyInitializedVariant = gameInfo.variant;
9611 /* [HGM] send opening position in FRC to first engine */
9613 SendToProgram("force\n", cps);
9615 /* engine is now in force mode! Set flag to wake it up after first move. */
9616 setboardSpoiledMachineBlack = 1;
9620 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9621 SendToProgram(buf, cps);
9623 cps->maybeThinking = FALSE;
9624 cps->offeredDraw = 0;
9625 if (!appData.icsActive) {
9626 SendTimeControl(cps, movesPerSession, timeControl,
9627 timeIncrement, appData.searchDepth,
9630 if (appData.showThinking
9631 // [HGM] thinking: four options require thinking output to be sent
9632 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9634 SendToProgram("post\n", cps);
9636 SendToProgram("hard\n", cps);
9637 if (!appData.ponderNextMove) {
9638 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9639 it without being sure what state we are in first. "hard"
9640 is not a toggle, so that one is OK.
9642 SendToProgram("easy\n", cps);
9645 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9646 SendToProgram(buf, cps);
9648 cps->initDone = TRUE;
9649 ClearEngineOutputPane(cps == &second);
9654 StartChessProgram(cps)
9655 ChessProgramState *cps;
9660 if (appData.noChessProgram) return;
9661 cps->initDone = FALSE;
9663 if (strcmp(cps->host, "localhost") == 0) {
9664 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9665 } else if (*appData.remoteShell == NULLCHAR) {
9666 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9668 if (*appData.remoteUser == NULLCHAR) {
9669 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9672 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9673 cps->host, appData.remoteUser, cps->program);
9675 err = StartChildProcess(buf, "", &cps->pr);
9679 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9680 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9681 if(cps != &first) return;
9682 appData.noChessProgram = TRUE;
9685 // DisplayFatalError(buf, err, 1);
9686 // cps->pr = NoProc;
9691 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9692 if (cps->protocolVersion > 1) {
9693 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9694 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9695 cps->comboCnt = 0; // and values of combo boxes
9696 SendToProgram(buf, cps);
9698 SendToProgram("xboard\n", cps);
9703 TwoMachinesEventIfReady P((void))
9705 static int curMess = 0;
9706 if (first.lastPing != first.lastPong) {
9707 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9708 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9711 if (second.lastPing != second.lastPong) {
9712 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9713 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9716 DisplayMessage("", ""); curMess = 0;
9722 MakeName(char *template)
9726 static char buf[MSG_SIZ];
9730 clock = time((time_t *)NULL);
9731 tm = localtime(&clock);
9733 while(*p++ = *template++) if(p[-1] == '%') {
9734 switch(*template++) {
9735 case 0: *p = 0; return buf;
9736 case 'Y': i = tm->tm_year+1900; break;
9737 case 'y': i = tm->tm_year-100; break;
9738 case 'M': i = tm->tm_mon+1; break;
9739 case 'd': i = tm->tm_mday; break;
9740 case 'h': i = tm->tm_hour; break;
9741 case 'm': i = tm->tm_min; break;
9742 case 's': i = tm->tm_sec; break;
9745 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9751 CountPlayers(char *p)
9754 while(p = strchr(p, '\n')) p++, n++; // count participants
9759 WriteTourneyFile(char *results, FILE *f)
9760 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9761 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9762 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9763 // create a file with tournament description
9764 fprintf(f, "-participants {%s}\n", appData.participants);
9765 fprintf(f, "-seedBase %d\n", appData.seedBase);
9766 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9767 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9768 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9769 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9770 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9771 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9772 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9773 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9774 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9775 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9776 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9777 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9779 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9781 fprintf(f, "-mps %d\n", appData.movesPerSession);
9782 fprintf(f, "-tc %s\n", appData.timeControl);
9783 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9785 fprintf(f, "-results \"%s\"\n", results);
9790 #define MAXENGINES 1000
9791 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9793 void Substitute(char *participants, int expunge)
9795 int i, changed, changes=0, nPlayers=0;
9796 char *p, *q, *r, buf[MSG_SIZ];
9797 if(participants == NULL) return;
9798 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9799 r = p = participants; q = appData.participants;
9800 while(*p && *p == *q) {
9801 if(*p == '\n') r = p+1, nPlayers++;
9804 if(*p) { // difference
9805 while(*p && *p++ != '\n');
9806 while(*q && *q++ != '\n');
9808 changes = 1 + (strcmp(p, q) != 0);
9810 if(changes == 1) { // a single engine mnemonic was changed
9811 q = r; while(*q) nPlayers += (*q++ == '\n');
9812 p = buf; while(*r && (*p = *r++) != '\n') p++;
9814 NamesToList(firstChessProgramNames, command, mnemonic);
9815 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9816 if(mnemonic[i]) { // The substitute is valid
9818 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9819 flock(fileno(f), LOCK_EX);
9820 ParseArgsFromFile(f);
9821 fseek(f, 0, SEEK_SET);
9822 FREE(appData.participants); appData.participants = participants;
9823 if(expunge) { // erase results of replaced engine
9824 int len = strlen(appData.results), w, b, dummy;
9825 for(i=0; i<len; i++) {
9826 Pairing(i, nPlayers, &w, &b, &dummy);
9827 if((w == changed || b == changed) && appData.results[i] == '*') {
9828 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9833 for(i=0; i<len; i++) {
9834 Pairing(i, nPlayers, &w, &b, &dummy);
9835 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9838 WriteTourneyFile(appData.results, f);
9839 fclose(f); // release lock
9842 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9844 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9845 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9851 CreateTourney(char *name)
9854 if(matchMode && strcmp(name, appData.tourneyFile)) {
9855 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9857 if(name[0] == NULLCHAR) {
9858 if(appData.participants[0])
9859 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9862 f = fopen(name, "r");
9863 if(f) { // file exists
9864 ASSIGN(appData.tourneyFile, name);
9865 ParseArgsFromFile(f); // parse it
9867 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9868 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9869 DisplayError(_("Not enough participants"), 0);
9872 ASSIGN(appData.tourneyFile, name);
9873 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9874 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9877 appData.noChessProgram = FALSE;
9878 appData.clockMode = TRUE;
9883 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9885 char buf[MSG_SIZ], *p, *q;
9889 while(*p && *p != '\n') *q++ = *p++;
9891 if(engineList[i]) free(engineList[i]);
9892 engineList[i] = strdup(buf);
9894 TidyProgramName(engineList[i], "localhost", buf);
9895 if(engineMnemonic[i]) free(engineMnemonic[i]);
9896 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9898 sscanf(q + 8, "%s", buf + strlen(buf));
9901 engineMnemonic[i] = strdup(buf);
9903 if(i > MAXENGINES - 2) break;
9905 engineList[i] = engineMnemonic[i] = NULL;
9908 // following implemented as macro to avoid type limitations
9909 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9911 void SwapEngines(int n)
9912 { // swap settings for first engine and other engine (so far only some selected options)
9917 SWAP(chessProgram, p)
9919 SWAP(hasOwnBookUCI, h)
9920 SWAP(protocolVersion, h)
9922 SWAP(scoreIsAbsolute, h)
9930 SetPlayer(int player)
9931 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9933 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9934 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9935 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9936 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9938 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9939 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9940 appData.firstHasOwnBookUCI = !appData.defNoBook;
9941 ParseArgsFromString(buf);
9947 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9948 { // determine players from game number
9949 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9951 if(appData.tourneyType == 0) {
9952 roundsPerCycle = (nPlayers - 1) | 1;
9953 pairingsPerRound = nPlayers / 2;
9954 } else if(appData.tourneyType > 0) {
9955 roundsPerCycle = nPlayers - appData.tourneyType;
9956 pairingsPerRound = appData.tourneyType;
9958 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9959 gamesPerCycle = gamesPerRound * roundsPerCycle;
9960 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9961 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9962 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9963 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9964 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9965 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9967 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9968 if(appData.roundSync) *syncInterval = gamesPerRound;
9970 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9972 if(appData.tourneyType == 0) {
9973 if(curPairing == (nPlayers-1)/2 ) {
9974 *whitePlayer = curRound;
9975 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9977 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9978 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9979 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9980 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9982 } else if(appData.tourneyType > 0) {
9983 *whitePlayer = curPairing;
9984 *blackPlayer = curRound + appData.tourneyType;
9987 // take care of white/black alternation per round.
9988 // For cycles and games this is already taken care of by default, derived from matchGame!
9989 return curRound & 1;
9993 NextTourneyGame(int nr, int *swapColors)
9994 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9996 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9998 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9999 tf = fopen(appData.tourneyFile, "r");
10000 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10001 ParseArgsFromFile(tf); fclose(tf);
10002 InitTimeControls(); // TC might be altered from tourney file
10004 nPlayers = CountPlayers(appData.participants); // count participants
10005 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10006 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10009 p = q = appData.results;
10010 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10011 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10012 DisplayMessage(_("Waiting for other game(s)"),"");
10013 waitingForGame = TRUE;
10014 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10017 waitingForGame = FALSE;
10020 if(appData.tourneyType < 0) {
10021 if(nr>=0 && !pairingReceived) {
10023 if(pairing.pr == NoProc) {
10024 if(!appData.pairingEngine[0]) {
10025 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10028 StartChessProgram(&pairing); // starts the pairing engine
10030 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10031 SendToProgram(buf, &pairing);
10032 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10033 SendToProgram(buf, &pairing);
10034 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10036 pairingReceived = 0; // ... so we continue here
10038 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10039 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10040 matchGame = 1; roundNr = nr / syncInterval + 1;
10043 if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10045 // redefine engines, engine dir, etc.
10046 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10047 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10049 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10050 SwapEngines(1); // and make that valid for second engine by swapping
10051 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10052 InitEngine(&second, 1);
10053 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10054 UpdateLogos(FALSE); // leave display to ModeHiglight()
10060 { // performs game initialization that does not invoke engines, and then tries to start the game
10061 int res, firstWhite, swapColors = 0;
10062 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10063 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10064 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10065 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10066 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10067 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10068 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10069 Reset(FALSE, first.pr != NoProc);
10070 res = LoadGameOrPosition(matchGame); // setup game
10071 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10072 if(!res) return; // abort when bad game/pos file
10073 TwoMachinesEvent();
10076 void UserAdjudicationEvent( int result )
10078 ChessMove gameResult = GameIsDrawn;
10081 gameResult = WhiteWins;
10083 else if( result < 0 ) {
10084 gameResult = BlackWins;
10087 if( gameMode == TwoMachinesPlay ) {
10088 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10093 // [HGM] save: calculate checksum of game to make games easily identifiable
10094 int StringCheckSum(char *s)
10097 if(s==NULL) return 0;
10098 while(*s) i = i*259 + *s++;
10105 for(i=backwardMostMove; i<forwardMostMove; i++) {
10106 sum += pvInfoList[i].depth;
10107 sum += StringCheckSum(parseList[i]);
10108 sum += StringCheckSum(commentList[i]);
10111 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10112 return sum + StringCheckSum(commentList[i]);
10113 } // end of save patch
10116 GameEnds(result, resultDetails, whosays)
10118 char *resultDetails;
10121 GameMode nextGameMode;
10123 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10125 if(endingGame) return; /* [HGM] crash: forbid recursion */
10127 if(twoBoards) { // [HGM] dual: switch back to one board
10128 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10129 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10131 if (appData.debugMode) {
10132 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10133 result, resultDetails ? resultDetails : "(null)", whosays);
10136 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10138 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10139 /* If we are playing on ICS, the server decides when the
10140 game is over, but the engine can offer to draw, claim
10144 if (appData.zippyPlay && first.initDone) {
10145 if (result == GameIsDrawn) {
10146 /* In case draw still needs to be claimed */
10147 SendToICS(ics_prefix);
10148 SendToICS("draw\n");
10149 } else if (StrCaseStr(resultDetails, "resign")) {
10150 SendToICS(ics_prefix);
10151 SendToICS("resign\n");
10155 endingGame = 0; /* [HGM] crash */
10159 /* If we're loading the game from a file, stop */
10160 if (whosays == GE_FILE) {
10161 (void) StopLoadGameTimer();
10165 /* Cancel draw offers */
10166 first.offeredDraw = second.offeredDraw = 0;
10168 /* If this is an ICS game, only ICS can really say it's done;
10169 if not, anyone can. */
10170 isIcsGame = (gameMode == IcsPlayingWhite ||
10171 gameMode == IcsPlayingBlack ||
10172 gameMode == IcsObserving ||
10173 gameMode == IcsExamining);
10175 if (!isIcsGame || whosays == GE_ICS) {
10176 /* OK -- not an ICS game, or ICS said it was done */
10178 if (!isIcsGame && !appData.noChessProgram)
10179 SetUserThinkingEnables();
10181 /* [HGM] if a machine claims the game end we verify this claim */
10182 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10183 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10185 ChessMove trueResult = (ChessMove) -1;
10187 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10188 first.twoMachinesColor[0] :
10189 second.twoMachinesColor[0] ;
10191 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10192 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10193 /* [HGM] verify: engine mate claims accepted if they were flagged */
10194 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10196 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10197 /* [HGM] verify: engine mate claims accepted if they were flagged */
10198 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10200 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10201 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10204 // now verify win claims, but not in drop games, as we don't understand those yet
10205 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10206 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10207 (result == WhiteWins && claimer == 'w' ||
10208 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10209 if (appData.debugMode) {
10210 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10211 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10213 if(result != trueResult) {
10214 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10215 result = claimer == 'w' ? BlackWins : WhiteWins;
10216 resultDetails = buf;
10219 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10220 && (forwardMostMove <= backwardMostMove ||
10221 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10222 (claimer=='b')==(forwardMostMove&1))
10224 /* [HGM] verify: draws that were not flagged are false claims */
10225 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10226 result = claimer == 'w' ? BlackWins : WhiteWins;
10227 resultDetails = buf;
10229 /* (Claiming a loss is accepted no questions asked!) */
10231 /* [HGM] bare: don't allow bare King to win */
10232 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10233 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10234 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10235 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10236 && result != GameIsDrawn)
10237 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10238 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10239 int p = (signed char)boards[forwardMostMove][i][j] - color;
10240 if(p >= 0 && p <= (int)WhiteKing) k++;
10242 if (appData.debugMode) {
10243 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10244 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10247 result = GameIsDrawn;
10248 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10249 resultDetails = buf;
10255 if(serverMoves != NULL && !loadFlag) { char c = '=';
10256 if(result==WhiteWins) c = '+';
10257 if(result==BlackWins) c = '-';
10258 if(resultDetails != NULL)
10259 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10261 if (resultDetails != NULL) {
10262 gameInfo.result = result;
10263 gameInfo.resultDetails = StrSave(resultDetails);
10265 /* display last move only if game was not loaded from file */
10266 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10267 DisplayMove(currentMove - 1);
10269 if (forwardMostMove != 0) {
10270 if (gameMode != PlayFromGameFile && gameMode != EditGame
10271 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10273 if (*appData.saveGameFile != NULLCHAR) {
10274 SaveGameToFile(appData.saveGameFile, TRUE);
10275 } else if (appData.autoSaveGames) {
10278 if (*appData.savePositionFile != NULLCHAR) {
10279 SavePositionToFile(appData.savePositionFile);
10284 /* Tell program how game ended in case it is learning */
10285 /* [HGM] Moved this to after saving the PGN, just in case */
10286 /* engine died and we got here through time loss. In that */
10287 /* case we will get a fatal error writing the pipe, which */
10288 /* would otherwise lose us the PGN. */
10289 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10290 /* output during GameEnds should never be fatal anymore */
10291 if (gameMode == MachinePlaysWhite ||
10292 gameMode == MachinePlaysBlack ||
10293 gameMode == TwoMachinesPlay ||
10294 gameMode == IcsPlayingWhite ||
10295 gameMode == IcsPlayingBlack ||
10296 gameMode == BeginningOfGame) {
10298 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10300 if (first.pr != NoProc) {
10301 SendToProgram(buf, &first);
10303 if (second.pr != NoProc &&
10304 gameMode == TwoMachinesPlay) {
10305 SendToProgram(buf, &second);
10310 if (appData.icsActive) {
10311 if (appData.quietPlay &&
10312 (gameMode == IcsPlayingWhite ||
10313 gameMode == IcsPlayingBlack)) {
10314 SendToICS(ics_prefix);
10315 SendToICS("set shout 1\n");
10317 nextGameMode = IcsIdle;
10318 ics_user_moved = FALSE;
10319 /* clean up premove. It's ugly when the game has ended and the
10320 * premove highlights are still on the board.
10323 gotPremove = FALSE;
10324 ClearPremoveHighlights();
10325 DrawPosition(FALSE, boards[currentMove]);
10327 if (whosays == GE_ICS) {
10330 if (gameMode == IcsPlayingWhite)
10332 else if(gameMode == IcsPlayingBlack)
10333 PlayIcsLossSound();
10336 if (gameMode == IcsPlayingBlack)
10338 else if(gameMode == IcsPlayingWhite)
10339 PlayIcsLossSound();
10342 PlayIcsDrawSound();
10345 PlayIcsUnfinishedSound();
10348 } else if (gameMode == EditGame ||
10349 gameMode == PlayFromGameFile ||
10350 gameMode == AnalyzeMode ||
10351 gameMode == AnalyzeFile) {
10352 nextGameMode = gameMode;
10354 nextGameMode = EndOfGame;
10359 nextGameMode = gameMode;
10362 if (appData.noChessProgram) {
10363 gameMode = nextGameMode;
10365 endingGame = 0; /* [HGM] crash */
10370 /* Put first chess program into idle state */
10371 if (first.pr != NoProc &&
10372 (gameMode == MachinePlaysWhite ||
10373 gameMode == MachinePlaysBlack ||
10374 gameMode == TwoMachinesPlay ||
10375 gameMode == IcsPlayingWhite ||
10376 gameMode == IcsPlayingBlack ||
10377 gameMode == BeginningOfGame)) {
10378 SendToProgram("force\n", &first);
10379 if (first.usePing) {
10381 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10382 SendToProgram(buf, &first);
10385 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10386 /* Kill off first chess program */
10387 if (first.isr != NULL)
10388 RemoveInputSource(first.isr);
10391 if (first.pr != NoProc) {
10393 DoSleep( appData.delayBeforeQuit );
10394 SendToProgram("quit\n", &first);
10395 DoSleep( appData.delayAfterQuit );
10396 DestroyChildProcess(first.pr, first.useSigterm);
10400 if (second.reuse) {
10401 /* Put second chess program into idle state */
10402 if (second.pr != NoProc &&
10403 gameMode == TwoMachinesPlay) {
10404 SendToProgram("force\n", &second);
10405 if (second.usePing) {
10407 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10408 SendToProgram(buf, &second);
10411 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10412 /* Kill off second chess program */
10413 if (second.isr != NULL)
10414 RemoveInputSource(second.isr);
10417 if (second.pr != NoProc) {
10418 DoSleep( appData.delayBeforeQuit );
10419 SendToProgram("quit\n", &second);
10420 DoSleep( appData.delayAfterQuit );
10421 DestroyChildProcess(second.pr, second.useSigterm);
10423 second.pr = NoProc;
10426 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10427 char resChar = '=';
10431 if (first.twoMachinesColor[0] == 'w') {
10434 second.matchWins++;
10439 if (first.twoMachinesColor[0] == 'b') {
10442 second.matchWins++;
10445 case GameUnfinished:
10451 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10452 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10453 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10454 ReserveGame(nextGame, resChar); // sets nextGame
10455 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10456 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10457 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10459 if (nextGame <= appData.matchGames && !abortMatch) {
10460 gameMode = nextGameMode;
10461 matchGame = nextGame; // this will be overruled in tourney mode!
10462 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10463 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10464 endingGame = 0; /* [HGM] crash */
10467 gameMode = nextGameMode;
10468 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10469 first.tidy, second.tidy,
10470 first.matchWins, second.matchWins,
10471 appData.matchGames - (first.matchWins + second.matchWins));
10472 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10473 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10474 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10475 first.twoMachinesColor = "black\n";
10476 second.twoMachinesColor = "white\n";
10478 first.twoMachinesColor = "white\n";
10479 second.twoMachinesColor = "black\n";
10483 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10484 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10486 gameMode = nextGameMode;
10488 endingGame = 0; /* [HGM] crash */
10489 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10490 if(matchMode == TRUE) { // match through command line: exit with or without popup
10492 ToNrEvent(forwardMostMove);
10493 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10495 } else DisplayFatalError(buf, 0, 0);
10496 } else { // match through menu; just stop, with or without popup
10497 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10500 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10501 } else DisplayNote(buf);
10503 if(ranking) free(ranking);
10507 /* Assumes program was just initialized (initString sent).
10508 Leaves program in force mode. */
10510 FeedMovesToProgram(cps, upto)
10511 ChessProgramState *cps;
10516 if (appData.debugMode)
10517 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10518 startedFromSetupPosition ? "position and " : "",
10519 backwardMostMove, upto, cps->which);
10520 if(currentlyInitializedVariant != gameInfo.variant) {
10522 // [HGM] variantswitch: make engine aware of new variant
10523 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10524 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10525 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10526 SendToProgram(buf, cps);
10527 currentlyInitializedVariant = gameInfo.variant;
10529 SendToProgram("force\n", cps);
10530 if (startedFromSetupPosition) {
10531 SendBoard(cps, backwardMostMove);
10532 if (appData.debugMode) {
10533 fprintf(debugFP, "feedMoves\n");
10536 for (i = backwardMostMove; i < upto; i++) {
10537 SendMoveToProgram(i, cps);
10543 ResurrectChessProgram()
10545 /* The chess program may have exited.
10546 If so, restart it and feed it all the moves made so far. */
10547 static int doInit = 0;
10549 if (appData.noChessProgram) return 1;
10551 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10552 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10553 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10554 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10556 if (first.pr != NoProc) return 1;
10557 StartChessProgram(&first);
10559 InitChessProgram(&first, FALSE);
10560 FeedMovesToProgram(&first, currentMove);
10562 if (!first.sendTime) {
10563 /* can't tell gnuchess what its clock should read,
10564 so we bow to its notion. */
10566 timeRemaining[0][currentMove] = whiteTimeRemaining;
10567 timeRemaining[1][currentMove] = blackTimeRemaining;
10570 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10571 appData.icsEngineAnalyze) && first.analysisSupport) {
10572 SendToProgram("analyze\n", &first);
10573 first.analyzing = TRUE;
10579 * Button procedures
10582 Reset(redraw, init)
10587 if (appData.debugMode) {
10588 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10589 redraw, init, gameMode);
10591 CleanupTail(); // [HGM] vari: delete any stored variations
10592 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10593 pausing = pauseExamInvalid = FALSE;
10594 startedFromSetupPosition = blackPlaysFirst = FALSE;
10596 whiteFlag = blackFlag = FALSE;
10597 userOfferedDraw = FALSE;
10598 hintRequested = bookRequested = FALSE;
10599 first.maybeThinking = FALSE;
10600 second.maybeThinking = FALSE;
10601 first.bookSuspend = FALSE; // [HGM] book
10602 second.bookSuspend = FALSE;
10603 thinkOutput[0] = NULLCHAR;
10604 lastHint[0] = NULLCHAR;
10605 ClearGameInfo(&gameInfo);
10606 gameInfo.variant = StringToVariant(appData.variant);
10607 ics_user_moved = ics_clock_paused = FALSE;
10608 ics_getting_history = H_FALSE;
10610 white_holding[0] = black_holding[0] = NULLCHAR;
10611 ClearProgramStats();
10612 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10616 flipView = appData.flipView;
10617 ClearPremoveHighlights();
10618 gotPremove = FALSE;
10619 alarmSounded = FALSE;
10621 GameEnds(EndOfFile, NULL, GE_PLAYER);
10622 if(appData.serverMovesName != NULL) {
10623 /* [HGM] prepare to make moves file for broadcasting */
10624 clock_t t = clock();
10625 if(serverMoves != NULL) fclose(serverMoves);
10626 serverMoves = fopen(appData.serverMovesName, "r");
10627 if(serverMoves != NULL) {
10628 fclose(serverMoves);
10629 /* delay 15 sec before overwriting, so all clients can see end */
10630 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10632 serverMoves = fopen(appData.serverMovesName, "w");
10636 gameMode = BeginningOfGame;
10638 if(appData.icsActive) gameInfo.variant = VariantNormal;
10639 currentMove = forwardMostMove = backwardMostMove = 0;
10640 InitPosition(redraw);
10641 for (i = 0; i < MAX_MOVES; i++) {
10642 if (commentList[i] != NULL) {
10643 free(commentList[i]);
10644 commentList[i] = NULL;
10648 timeRemaining[0][0] = whiteTimeRemaining;
10649 timeRemaining[1][0] = blackTimeRemaining;
10651 if (first.pr == NoProc) {
10652 StartChessProgram(&first);
10655 InitChessProgram(&first, startedFromSetupPosition);
10658 DisplayMessage("", "");
10659 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10660 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10667 if (!AutoPlayOneMove())
10669 if (matchMode || appData.timeDelay == 0)
10671 if (appData.timeDelay < 0)
10673 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10682 int fromX, fromY, toX, toY;
10684 if (appData.debugMode) {
10685 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10688 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10691 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10692 pvInfoList[currentMove].depth = programStats.depth;
10693 pvInfoList[currentMove].score = programStats.score;
10694 pvInfoList[currentMove].time = 0;
10695 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10698 if (currentMove >= forwardMostMove) {
10699 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10700 // gameMode = EndOfGame;
10701 // ModeHighlight();
10703 /* [AS] Clear current move marker at the end of a game */
10704 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10709 toX = moveList[currentMove][2] - AAA;
10710 toY = moveList[currentMove][3] - ONE;
10712 if (moveList[currentMove][1] == '@') {
10713 if (appData.highlightLastMove) {
10714 SetHighlights(-1, -1, toX, toY);
10717 fromX = moveList[currentMove][0] - AAA;
10718 fromY = moveList[currentMove][1] - ONE;
10720 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10722 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10724 if (appData.highlightLastMove) {
10725 SetHighlights(fromX, fromY, toX, toY);
10728 DisplayMove(currentMove);
10729 SendMoveToProgram(currentMove++, &first);
10730 DisplayBothClocks();
10731 DrawPosition(FALSE, boards[currentMove]);
10732 // [HGM] PV info: always display, routine tests if empty
10733 DisplayComment(currentMove - 1, commentList[currentMove]);
10739 LoadGameOneMove(readAhead)
10740 ChessMove readAhead;
10742 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10743 char promoChar = NULLCHAR;
10744 ChessMove moveType;
10745 char move[MSG_SIZ];
10748 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10749 gameMode != AnalyzeMode && gameMode != Training) {
10754 yyboardindex = forwardMostMove;
10755 if (readAhead != EndOfFile) {
10756 moveType = readAhead;
10758 if (gameFileFP == NULL)
10760 moveType = (ChessMove) Myylex();
10764 switch (moveType) {
10766 if (appData.debugMode)
10767 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10770 /* append the comment but don't display it */
10771 AppendComment(currentMove, p, FALSE);
10774 case WhiteCapturesEnPassant:
10775 case BlackCapturesEnPassant:
10776 case WhitePromotion:
10777 case BlackPromotion:
10778 case WhiteNonPromotion:
10779 case BlackNonPromotion:
10781 case WhiteKingSideCastle:
10782 case WhiteQueenSideCastle:
10783 case BlackKingSideCastle:
10784 case BlackQueenSideCastle:
10785 case WhiteKingSideCastleWild:
10786 case WhiteQueenSideCastleWild:
10787 case BlackKingSideCastleWild:
10788 case BlackQueenSideCastleWild:
10790 case WhiteHSideCastleFR:
10791 case WhiteASideCastleFR:
10792 case BlackHSideCastleFR:
10793 case BlackASideCastleFR:
10795 if (appData.debugMode)
10796 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10797 fromX = currentMoveString[0] - AAA;
10798 fromY = currentMoveString[1] - ONE;
10799 toX = currentMoveString[2] - AAA;
10800 toY = currentMoveString[3] - ONE;
10801 promoChar = currentMoveString[4];
10806 if (appData.debugMode)
10807 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10808 fromX = moveType == WhiteDrop ?
10809 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10810 (int) CharToPiece(ToLower(currentMoveString[0]));
10812 toX = currentMoveString[2] - AAA;
10813 toY = currentMoveString[3] - ONE;
10819 case GameUnfinished:
10820 if (appData.debugMode)
10821 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10822 p = strchr(yy_text, '{');
10823 if (p == NULL) p = strchr(yy_text, '(');
10826 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10828 q = strchr(p, *p == '{' ? '}' : ')');
10829 if (q != NULL) *q = NULLCHAR;
10832 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10833 GameEnds(moveType, p, GE_FILE);
10835 if (cmailMsgLoaded) {
10837 flipView = WhiteOnMove(currentMove);
10838 if (moveType == GameUnfinished) flipView = !flipView;
10839 if (appData.debugMode)
10840 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10845 if (appData.debugMode)
10846 fprintf(debugFP, "Parser hit end of file\n");
10847 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10853 if (WhiteOnMove(currentMove)) {
10854 GameEnds(BlackWins, "Black mates", GE_FILE);
10856 GameEnds(WhiteWins, "White mates", GE_FILE);
10860 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10866 case MoveNumberOne:
10867 if (lastLoadGameStart == GNUChessGame) {
10868 /* GNUChessGames have numbers, but they aren't move numbers */
10869 if (appData.debugMode)
10870 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10871 yy_text, (int) moveType);
10872 return LoadGameOneMove(EndOfFile); /* tail recursion */
10874 /* else fall thru */
10879 /* Reached start of next game in file */
10880 if (appData.debugMode)
10881 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10882 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10888 if (WhiteOnMove(currentMove)) {
10889 GameEnds(BlackWins, "Black mates", GE_FILE);
10891 GameEnds(WhiteWins, "White mates", GE_FILE);
10895 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10901 case PositionDiagram: /* should not happen; ignore */
10902 case ElapsedTime: /* ignore */
10903 case NAG: /* ignore */
10904 if (appData.debugMode)
10905 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10906 yy_text, (int) moveType);
10907 return LoadGameOneMove(EndOfFile); /* tail recursion */
10910 if (appData.testLegality) {
10911 if (appData.debugMode)
10912 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10913 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10914 (forwardMostMove / 2) + 1,
10915 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10916 DisplayError(move, 0);
10919 if (appData.debugMode)
10920 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10921 yy_text, currentMoveString);
10922 fromX = currentMoveString[0] - AAA;
10923 fromY = currentMoveString[1] - ONE;
10924 toX = currentMoveString[2] - AAA;
10925 toY = currentMoveString[3] - ONE;
10926 promoChar = currentMoveString[4];
10930 case AmbiguousMove:
10931 if (appData.debugMode)
10932 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10933 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10934 (forwardMostMove / 2) + 1,
10935 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10936 DisplayError(move, 0);
10941 case ImpossibleMove:
10942 if (appData.debugMode)
10943 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10944 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10945 (forwardMostMove / 2) + 1,
10946 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947 DisplayError(move, 0);
10953 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10954 DrawPosition(FALSE, boards[currentMove]);
10955 DisplayBothClocks();
10956 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10957 DisplayComment(currentMove - 1, commentList[currentMove]);
10959 (void) StopLoadGameTimer();
10961 cmailOldMove = forwardMostMove;
10964 /* currentMoveString is set as a side-effect of yylex */
10966 thinkOutput[0] = NULLCHAR;
10967 MakeMove(fromX, fromY, toX, toY, promoChar);
10968 currentMove = forwardMostMove;
10973 /* Load the nth game from the given file */
10975 LoadGameFromFile(filename, n, title, useList)
10979 /*Boolean*/ int useList;
10984 if (strcmp(filename, "-") == 0) {
10988 f = fopen(filename, "rb");
10990 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10991 DisplayError(buf, errno);
10995 if (fseek(f, 0, 0) == -1) {
10996 /* f is not seekable; probably a pipe */
10999 if (useList && n == 0) {
11000 int error = GameListBuild(f);
11002 DisplayError(_("Cannot build game list"), error);
11003 } else if (!ListEmpty(&gameList) &&
11004 ((ListGame *) gameList.tailPred)->number > 1) {
11005 GameListPopUp(f, title);
11012 return LoadGame(f, n, title, FALSE);
11017 MakeRegisteredMove()
11019 int fromX, fromY, toX, toY;
11021 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11022 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11025 if (appData.debugMode)
11026 fprintf(debugFP, "Restoring %s for game %d\n",
11027 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11029 thinkOutput[0] = NULLCHAR;
11030 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11031 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11032 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11033 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11034 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11035 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11036 MakeMove(fromX, fromY, toX, toY, promoChar);
11037 ShowMove(fromX, fromY, toX, toY);
11039 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11046 if (WhiteOnMove(currentMove)) {
11047 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11049 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11054 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11061 if (WhiteOnMove(currentMove)) {
11062 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11064 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11069 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11080 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11082 CmailLoadGame(f, gameNumber, title, useList)
11090 if (gameNumber > nCmailGames) {
11091 DisplayError(_("No more games in this message"), 0);
11094 if (f == lastLoadGameFP) {
11095 int offset = gameNumber - lastLoadGameNumber;
11097 cmailMsg[0] = NULLCHAR;
11098 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11099 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11100 nCmailMovesRegistered--;
11102 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11103 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11104 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11107 if (! RegisterMove()) return FALSE;
11111 retVal = LoadGame(f, gameNumber, title, useList);
11113 /* Make move registered during previous look at this game, if any */
11114 MakeRegisteredMove();
11116 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11117 commentList[currentMove]
11118 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11119 DisplayComment(currentMove - 1, commentList[currentMove]);
11125 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11130 int gameNumber = lastLoadGameNumber + offset;
11131 if (lastLoadGameFP == NULL) {
11132 DisplayError(_("No game has been loaded yet"), 0);
11135 if (gameNumber <= 0) {
11136 DisplayError(_("Can't back up any further"), 0);
11139 if (cmailMsgLoaded) {
11140 return CmailLoadGame(lastLoadGameFP, gameNumber,
11141 lastLoadGameTitle, lastLoadGameUseList);
11143 return LoadGame(lastLoadGameFP, gameNumber,
11144 lastLoadGameTitle, lastLoadGameUseList);
11148 int keys[EmptySquare+1];
11151 PositionMatches(Board b1, Board b2)
11154 switch(appData.searchMode) {
11155 case 1: return CompareWithRights(b1, b2);
11157 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11158 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11162 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11163 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11164 sum += keys[b1[r][f]] - keys[b2[r][f]];
11168 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11169 sum += keys[b1[r][f]] - keys[b2[r][f]];
11181 int pieceList[256], quickBoard[256];
11182 ChessSquare pieceType[256] = { EmptySquare };
11183 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11184 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11185 int soughtTotal, turn;
11186 Boolean epOK, flipSearch;
11189 unsigned char piece, to;
11192 #define DSIZE (250000)
11194 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11195 Move *moveDatabase = initialSpace;
11196 unsigned int movePtr, dataSize = DSIZE;
11198 int MakePieceList(Board board, int *counts)
11200 int r, f, n=Q_PROMO, total=0;
11201 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11202 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11203 int sq = f + (r<<4);
11204 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11205 quickBoard[sq] = ++n;
11207 pieceType[n] = board[r][f];
11208 counts[board[r][f]]++;
11209 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11210 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11214 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11218 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11220 int sq = fromX + (fromY<<4);
11221 int piece = quickBoard[sq];
11222 quickBoard[sq] = 0;
11223 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11224 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11225 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11226 moveDatabase[movePtr++].piece = Q_WCASTL;
11227 quickBoard[sq] = piece;
11228 piece = quickBoard[from]; quickBoard[from] = 0;
11229 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11231 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11232 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11233 moveDatabase[movePtr++].piece = Q_BCASTL;
11234 quickBoard[sq] = piece;
11235 piece = quickBoard[from]; quickBoard[from] = 0;
11236 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11238 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11239 quickBoard[(fromY<<4)+toX] = 0;
11240 moveDatabase[movePtr].piece = Q_EP;
11241 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11242 moveDatabase[movePtr].to = sq;
11244 if(promoPiece != pieceType[piece]) {
11245 moveDatabase[movePtr++].piece = Q_PROMO;
11246 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11248 moveDatabase[movePtr].piece = piece;
11249 quickBoard[sq] = piece;
11253 int PackGame(Board board)
11255 Move *newSpace = NULL;
11256 moveDatabase[movePtr].piece = 0; // terminate previous game
11257 if(movePtr > dataSize) {
11258 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11259 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11260 if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11263 Move *p = moveDatabase, *q = newSpace;
11264 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11265 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11266 moveDatabase = newSpace;
11267 } else { // calloc failed, we must be out of memory. Too bad...
11268 dataSize = 0; // prevent calloc events for all subsequent games
11269 return 0; // and signal this one isn't cached
11273 MakePieceList(board, counts);
11277 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11278 { // compare according to search mode
11280 switch(appData.searchMode)
11282 case 1: // exact position match
11283 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11284 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11285 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11288 case 2: // can have extra material on empty squares
11289 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11290 if(board[r][f] == EmptySquare) continue;
11291 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11294 case 3: // material with exact Pawn structure
11295 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11296 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11297 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11298 } // fall through to material comparison
11299 case 4: // exact material
11300 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11302 case 6: // material range with given imbalance
11303 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11304 // fall through to range comparison
11305 case 5: // material range
11306 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11311 int QuickScan(Board board, Move *move)
11312 { // reconstruct game,and compare all positions in it
11313 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11315 int piece = move->piece;
11316 int to = move->to, from = pieceList[piece];
11317 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11318 if(!piece) return -1;
11319 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11320 piece = (++move)->piece;
11321 from = pieceList[piece];
11322 counts[pieceType[piece]]--;
11323 pieceType[piece] = (ChessSquare) move->to;
11324 counts[move->to]++;
11325 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11326 counts[pieceType[quickBoard[to]]]--;
11327 quickBoard[to] = 0; total--;
11330 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11331 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11332 from = pieceList[piece]; // so this must be King
11333 quickBoard[from] = 0;
11334 quickBoard[to] = piece;
11335 pieceList[piece] = to;
11340 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11341 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11342 quickBoard[from] = 0;
11343 quickBoard[to] = piece;
11344 pieceList[piece] = to;
11346 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11347 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11348 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11349 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11351 static int lastCounts[EmptySquare+1];
11353 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11354 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11355 } else stretch = 0;
11356 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11364 flipSearch = FALSE;
11365 CopyBoard(soughtBoard, boards[currentMove]);
11366 soughtTotal = MakePieceList(soughtBoard, maxSought);
11367 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11368 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11369 CopyBoard(reverseBoard, boards[currentMove]);
11370 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11371 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11372 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11373 reverseBoard[r][f] = piece;
11375 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11376 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11377 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11378 || (boards[currentMove][CASTLING][2] == NoRights ||
11379 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11380 && (boards[currentMove][CASTLING][5] == NoRights ||
11381 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11384 CopyBoard(flipBoard, soughtBoard);
11385 CopyBoard(rotateBoard, reverseBoard);
11386 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11387 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11388 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11391 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11392 if(appData.searchMode >= 5) {
11393 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11394 MakePieceList(soughtBoard, minSought);
11395 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11397 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11398 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11401 GameInfo dummyInfo;
11403 int GameContainsPosition(FILE *f, ListGame *lg)
11405 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11406 int fromX, fromY, toX, toY;
11408 static int initDone=FALSE;
11410 // weed out games based on numerical tag comparison
11411 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11412 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11413 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11414 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11416 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11419 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11420 else CopyBoard(boards[scratch], initialPosition); // default start position
11423 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11424 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11427 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11428 fseek(f, lg->offset, 0);
11431 yyboardindex = scratch;
11432 quickFlag = plyNr+1;
11437 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11443 if(plyNr) return -1; // after we have seen moves, this is for new game
11446 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11447 case ImpossibleMove:
11448 case WhiteWins: // game ends here with these four
11451 case GameUnfinished:
11455 if(appData.testLegality) return -1;
11456 case WhiteCapturesEnPassant:
11457 case BlackCapturesEnPassant:
11458 case WhitePromotion:
11459 case BlackPromotion:
11460 case WhiteNonPromotion:
11461 case BlackNonPromotion:
11463 case WhiteKingSideCastle:
11464 case WhiteQueenSideCastle:
11465 case BlackKingSideCastle:
11466 case BlackQueenSideCastle:
11467 case WhiteKingSideCastleWild:
11468 case WhiteQueenSideCastleWild:
11469 case BlackKingSideCastleWild:
11470 case BlackQueenSideCastleWild:
11471 case WhiteHSideCastleFR:
11472 case WhiteASideCastleFR:
11473 case BlackHSideCastleFR:
11474 case BlackASideCastleFR:
11475 fromX = currentMoveString[0] - AAA;
11476 fromY = currentMoveString[1] - ONE;
11477 toX = currentMoveString[2] - AAA;
11478 toY = currentMoveString[3] - ONE;
11479 promoChar = currentMoveString[4];
11483 fromX = next == WhiteDrop ?
11484 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11485 (int) CharToPiece(ToLower(currentMoveString[0]));
11487 toX = currentMoveString[2] - AAA;
11488 toY = currentMoveString[3] - ONE;
11491 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11493 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11494 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11495 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11496 if(appData.findMirror) {
11497 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11498 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11503 /* Load the nth game from open file f */
11505 LoadGame(f, gameNumber, title, useList)
11513 int gn = gameNumber;
11514 ListGame *lg = NULL;
11515 int numPGNTags = 0;
11517 GameMode oldGameMode;
11518 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11520 if (appData.debugMode)
11521 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11523 if (gameMode == Training )
11524 SetTrainingModeOff();
11526 oldGameMode = gameMode;
11527 if (gameMode != BeginningOfGame) {
11528 Reset(FALSE, TRUE);
11532 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11533 fclose(lastLoadGameFP);
11537 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11540 fseek(f, lg->offset, 0);
11541 GameListHighlight(gameNumber);
11542 pos = lg->position;
11546 DisplayError(_("Game number out of range"), 0);
11551 if (fseek(f, 0, 0) == -1) {
11552 if (f == lastLoadGameFP ?
11553 gameNumber == lastLoadGameNumber + 1 :
11557 DisplayError(_("Can't seek on game file"), 0);
11562 lastLoadGameFP = f;
11563 lastLoadGameNumber = gameNumber;
11564 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11565 lastLoadGameUseList = useList;
11569 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11570 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11571 lg->gameInfo.black);
11573 } else if (*title != NULLCHAR) {
11574 if (gameNumber > 1) {
11575 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11578 DisplayTitle(title);
11582 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11583 gameMode = PlayFromGameFile;
11587 currentMove = forwardMostMove = backwardMostMove = 0;
11588 CopyBoard(boards[0], initialPosition);
11592 * Skip the first gn-1 games in the file.
11593 * Also skip over anything that precedes an identifiable
11594 * start of game marker, to avoid being confused by
11595 * garbage at the start of the file. Currently
11596 * recognized start of game markers are the move number "1",
11597 * the pattern "gnuchess .* game", the pattern
11598 * "^[#;%] [^ ]* game file", and a PGN tag block.
11599 * A game that starts with one of the latter two patterns
11600 * will also have a move number 1, possibly
11601 * following a position diagram.
11602 * 5-4-02: Let's try being more lenient and allowing a game to
11603 * start with an unnumbered move. Does that break anything?
11605 cm = lastLoadGameStart = EndOfFile;
11607 yyboardindex = forwardMostMove;
11608 cm = (ChessMove) Myylex();
11611 if (cmailMsgLoaded) {
11612 nCmailGames = CMAIL_MAX_GAMES - gn;
11615 DisplayError(_("Game not found in file"), 0);
11622 lastLoadGameStart = cm;
11625 case MoveNumberOne:
11626 switch (lastLoadGameStart) {
11631 case MoveNumberOne:
11633 gn--; /* count this game */
11634 lastLoadGameStart = cm;
11643 switch (lastLoadGameStart) {
11646 case MoveNumberOne:
11648 gn--; /* count this game */
11649 lastLoadGameStart = cm;
11652 lastLoadGameStart = cm; /* game counted already */
11660 yyboardindex = forwardMostMove;
11661 cm = (ChessMove) Myylex();
11662 } while (cm == PGNTag || cm == Comment);
11669 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11670 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11671 != CMAIL_OLD_RESULT) {
11673 cmailResult[ CMAIL_MAX_GAMES
11674 - gn - 1] = CMAIL_OLD_RESULT;
11680 /* Only a NormalMove can be at the start of a game
11681 * without a position diagram. */
11682 if (lastLoadGameStart == EndOfFile ) {
11684 lastLoadGameStart = MoveNumberOne;
11693 if (appData.debugMode)
11694 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11696 if (cm == XBoardGame) {
11697 /* Skip any header junk before position diagram and/or move 1 */
11699 yyboardindex = forwardMostMove;
11700 cm = (ChessMove) Myylex();
11702 if (cm == EndOfFile ||
11703 cm == GNUChessGame || cm == XBoardGame) {
11704 /* Empty game; pretend end-of-file and handle later */
11709 if (cm == MoveNumberOne || cm == PositionDiagram ||
11710 cm == PGNTag || cm == Comment)
11713 } else if (cm == GNUChessGame) {
11714 if (gameInfo.event != NULL) {
11715 free(gameInfo.event);
11717 gameInfo.event = StrSave(yy_text);
11720 startedFromSetupPosition = FALSE;
11721 while (cm == PGNTag) {
11722 if (appData.debugMode)
11723 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11724 err = ParsePGNTag(yy_text, &gameInfo);
11725 if (!err) numPGNTags++;
11727 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11728 if(gameInfo.variant != oldVariant) {
11729 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11730 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11731 InitPosition(TRUE);
11732 oldVariant = gameInfo.variant;
11733 if (appData.debugMode)
11734 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11738 if (gameInfo.fen != NULL) {
11739 Board initial_position;
11740 startedFromSetupPosition = TRUE;
11741 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11743 DisplayError(_("Bad FEN position in file"), 0);
11746 CopyBoard(boards[0], initial_position);
11747 if (blackPlaysFirst) {
11748 currentMove = forwardMostMove = backwardMostMove = 1;
11749 CopyBoard(boards[1], initial_position);
11750 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11751 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11752 timeRemaining[0][1] = whiteTimeRemaining;
11753 timeRemaining[1][1] = blackTimeRemaining;
11754 if (commentList[0] != NULL) {
11755 commentList[1] = commentList[0];
11756 commentList[0] = NULL;
11759 currentMove = forwardMostMove = backwardMostMove = 0;
11761 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11763 initialRulePlies = FENrulePlies;
11764 for( i=0; i< nrCastlingRights; i++ )
11765 initialRights[i] = initial_position[CASTLING][i];
11767 yyboardindex = forwardMostMove;
11768 free(gameInfo.fen);
11769 gameInfo.fen = NULL;
11772 yyboardindex = forwardMostMove;
11773 cm = (ChessMove) Myylex();
11775 /* Handle comments interspersed among the tags */
11776 while (cm == Comment) {
11778 if (appData.debugMode)
11779 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11781 AppendComment(currentMove, p, FALSE);
11782 yyboardindex = forwardMostMove;
11783 cm = (ChessMove) Myylex();
11787 /* don't rely on existence of Event tag since if game was
11788 * pasted from clipboard the Event tag may not exist
11790 if (numPGNTags > 0){
11792 if (gameInfo.variant == VariantNormal) {
11793 VariantClass v = StringToVariant(gameInfo.event);
11794 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11795 if(v < VariantShogi) gameInfo.variant = v;
11798 if( appData.autoDisplayTags ) {
11799 tags = PGNTags(&gameInfo);
11800 TagsPopUp(tags, CmailMsg());
11805 /* Make something up, but don't display it now */
11810 if (cm == PositionDiagram) {
11813 Board initial_position;
11815 if (appData.debugMode)
11816 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11818 if (!startedFromSetupPosition) {
11820 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11821 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11832 initial_position[i][j++] = CharToPiece(*p);
11835 while (*p == ' ' || *p == '\t' ||
11836 *p == '\n' || *p == '\r') p++;
11838 if (strncmp(p, "black", strlen("black"))==0)
11839 blackPlaysFirst = TRUE;
11841 blackPlaysFirst = FALSE;
11842 startedFromSetupPosition = TRUE;
11844 CopyBoard(boards[0], initial_position);
11845 if (blackPlaysFirst) {
11846 currentMove = forwardMostMove = backwardMostMove = 1;
11847 CopyBoard(boards[1], initial_position);
11848 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11849 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11850 timeRemaining[0][1] = whiteTimeRemaining;
11851 timeRemaining[1][1] = blackTimeRemaining;
11852 if (commentList[0] != NULL) {
11853 commentList[1] = commentList[0];
11854 commentList[0] = NULL;
11857 currentMove = forwardMostMove = backwardMostMove = 0;
11860 yyboardindex = forwardMostMove;
11861 cm = (ChessMove) Myylex();
11864 if (first.pr == NoProc) {
11865 StartChessProgram(&first);
11867 InitChessProgram(&first, FALSE);
11868 SendToProgram("force\n", &first);
11869 if (startedFromSetupPosition) {
11870 SendBoard(&first, forwardMostMove);
11871 if (appData.debugMode) {
11872 fprintf(debugFP, "Load Game\n");
11874 DisplayBothClocks();
11877 /* [HGM] server: flag to write setup moves in broadcast file as one */
11878 loadFlag = appData.suppressLoadMoves;
11880 while (cm == Comment) {
11882 if (appData.debugMode)
11883 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11885 AppendComment(currentMove, p, FALSE);
11886 yyboardindex = forwardMostMove;
11887 cm = (ChessMove) Myylex();
11890 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11891 cm == WhiteWins || cm == BlackWins ||
11892 cm == GameIsDrawn || cm == GameUnfinished) {
11893 DisplayMessage("", _("No moves in game"));
11894 if (cmailMsgLoaded) {
11895 if (appData.debugMode)
11896 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11900 DrawPosition(FALSE, boards[currentMove]);
11901 DisplayBothClocks();
11902 gameMode = EditGame;
11909 // [HGM] PV info: routine tests if comment empty
11910 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11911 DisplayComment(currentMove - 1, commentList[currentMove]);
11913 if (!matchMode && appData.timeDelay != 0)
11914 DrawPosition(FALSE, boards[currentMove]);
11916 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11917 programStats.ok_to_send = 1;
11920 /* if the first token after the PGN tags is a move
11921 * and not move number 1, retrieve it from the parser
11923 if (cm != MoveNumberOne)
11924 LoadGameOneMove(cm);
11926 /* load the remaining moves from the file */
11927 while (LoadGameOneMove(EndOfFile)) {
11928 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11929 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11932 /* rewind to the start of the game */
11933 currentMove = backwardMostMove;
11935 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11937 if (oldGameMode == AnalyzeFile ||
11938 oldGameMode == AnalyzeMode) {
11939 AnalyzeFileEvent();
11942 if (!matchMode && pos >= 0) {
11943 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11945 if (matchMode || appData.timeDelay == 0) {
11947 } else if (appData.timeDelay > 0) {
11948 AutoPlayGameLoop();
11951 if (appData.debugMode)
11952 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11954 loadFlag = 0; /* [HGM] true game starts */
11958 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11960 ReloadPosition(offset)
11963 int positionNumber = lastLoadPositionNumber + offset;
11964 if (lastLoadPositionFP == NULL) {
11965 DisplayError(_("No position has been loaded yet"), 0);
11968 if (positionNumber <= 0) {
11969 DisplayError(_("Can't back up any further"), 0);
11972 return LoadPosition(lastLoadPositionFP, positionNumber,
11973 lastLoadPositionTitle);
11976 /* Load the nth position from the given file */
11978 LoadPositionFromFile(filename, n, title)
11986 if (strcmp(filename, "-") == 0) {
11987 return LoadPosition(stdin, n, "stdin");
11989 f = fopen(filename, "rb");
11991 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11992 DisplayError(buf, errno);
11995 return LoadPosition(f, n, title);
12000 /* Load the nth position from the given open file, and close it */
12002 LoadPosition(f, positionNumber, title)
12004 int positionNumber;
12007 char *p, line[MSG_SIZ];
12008 Board initial_position;
12009 int i, j, fenMode, pn;
12011 if (gameMode == Training )
12012 SetTrainingModeOff();
12014 if (gameMode != BeginningOfGame) {
12015 Reset(FALSE, TRUE);
12017 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12018 fclose(lastLoadPositionFP);
12020 if (positionNumber == 0) positionNumber = 1;
12021 lastLoadPositionFP = f;
12022 lastLoadPositionNumber = positionNumber;
12023 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12024 if (first.pr == NoProc && !appData.noChessProgram) {
12025 StartChessProgram(&first);
12026 InitChessProgram(&first, FALSE);
12028 pn = positionNumber;
12029 if (positionNumber < 0) {
12030 /* Negative position number means to seek to that byte offset */
12031 if (fseek(f, -positionNumber, 0) == -1) {
12032 DisplayError(_("Can't seek on position file"), 0);
12037 if (fseek(f, 0, 0) == -1) {
12038 if (f == lastLoadPositionFP ?
12039 positionNumber == lastLoadPositionNumber + 1 :
12040 positionNumber == 1) {
12043 DisplayError(_("Can't seek on position file"), 0);
12048 /* See if this file is FEN or old-style xboard */
12049 if (fgets(line, MSG_SIZ, f) == NULL) {
12050 DisplayError(_("Position not found in file"), 0);
12053 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12054 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12057 if (fenMode || line[0] == '#') pn--;
12059 /* skip positions before number pn */
12060 if (fgets(line, MSG_SIZ, f) == NULL) {
12062 DisplayError(_("Position not found in file"), 0);
12065 if (fenMode || line[0] == '#') pn--;
12070 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12071 DisplayError(_("Bad FEN position in file"), 0);
12075 (void) fgets(line, MSG_SIZ, f);
12076 (void) fgets(line, MSG_SIZ, f);
12078 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12079 (void) fgets(line, MSG_SIZ, f);
12080 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12083 initial_position[i][j++] = CharToPiece(*p);
12087 blackPlaysFirst = FALSE;
12089 (void) fgets(line, MSG_SIZ, f);
12090 if (strncmp(line, "black", strlen("black"))==0)
12091 blackPlaysFirst = TRUE;
12094 startedFromSetupPosition = TRUE;
12096 CopyBoard(boards[0], initial_position);
12097 if (blackPlaysFirst) {
12098 currentMove = forwardMostMove = backwardMostMove = 1;
12099 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12100 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12101 CopyBoard(boards[1], initial_position);
12102 DisplayMessage("", _("Black to play"));
12104 currentMove = forwardMostMove = backwardMostMove = 0;
12105 DisplayMessage("", _("White to play"));
12107 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12108 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12109 SendToProgram("force\n", &first);
12110 SendBoard(&first, forwardMostMove);
12112 if (appData.debugMode) {
12114 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12115 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12116 fprintf(debugFP, "Load Position\n");
12119 if (positionNumber > 1) {
12120 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12121 DisplayTitle(line);
12123 DisplayTitle(title);
12125 gameMode = EditGame;
12128 timeRemaining[0][1] = whiteTimeRemaining;
12129 timeRemaining[1][1] = blackTimeRemaining;
12130 DrawPosition(FALSE, boards[currentMove]);
12137 CopyPlayerNameIntoFileName(dest, src)
12140 while (*src != NULLCHAR && *src != ',') {
12145 *(*dest)++ = *src++;
12150 char *DefaultFileName(ext)
12153 static char def[MSG_SIZ];
12156 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12158 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12160 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12162 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12169 /* Save the current game to the given file */
12171 SaveGameToFile(filename, append)
12177 int result, i, t,tot=0;
12179 if (strcmp(filename, "-") == 0) {
12180 return SaveGame(stdout, 0, NULL);
12182 for(i=0; i<10; i++) { // upto 10 tries
12183 f = fopen(filename, append ? "a" : "w");
12184 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12185 if(f || errno != 13) break;
12186 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12190 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12191 DisplayError(buf, errno);
12194 safeStrCpy(buf, lastMsg, MSG_SIZ);
12195 DisplayMessage(_("Waiting for access to save file"), "");
12196 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12197 DisplayMessage(_("Saving game"), "");
12198 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
12199 result = SaveGame(f, 0, NULL);
12200 DisplayMessage(buf, "");
12210 static char buf[MSG_SIZ];
12213 p = strchr(str, ' ');
12214 if (p == NULL) return str;
12215 strncpy(buf, str, p - str);
12216 buf[p - str] = NULLCHAR;
12220 #define PGN_MAX_LINE 75
12222 #define PGN_SIDE_WHITE 0
12223 #define PGN_SIDE_BLACK 1
12226 static int FindFirstMoveOutOfBook( int side )
12230 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12231 int index = backwardMostMove;
12232 int has_book_hit = 0;
12234 if( (index % 2) != side ) {
12238 while( index < forwardMostMove ) {
12239 /* Check to see if engine is in book */
12240 int depth = pvInfoList[index].depth;
12241 int score = pvInfoList[index].score;
12247 else if( score == 0 && depth == 63 ) {
12248 in_book = 1; /* Zappa */
12250 else if( score == 2 && depth == 99 ) {
12251 in_book = 1; /* Abrok */
12254 has_book_hit += in_book;
12270 void GetOutOfBookInfo( char * buf )
12274 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12276 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12277 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12281 if( oob[0] >= 0 || oob[1] >= 0 ) {
12282 for( i=0; i<2; i++ ) {
12286 if( i > 0 && oob[0] >= 0 ) {
12287 strcat( buf, " " );
12290 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12291 sprintf( buf+strlen(buf), "%s%.2f",
12292 pvInfoList[idx].score >= 0 ? "+" : "",
12293 pvInfoList[idx].score / 100.0 );
12299 /* Save game in PGN style and close the file */
12304 int i, offset, linelen, newblock;
12308 int movelen, numlen, blank;
12309 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12311 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12313 tm = time((time_t *) NULL);
12315 PrintPGNTags(f, &gameInfo);
12317 if (backwardMostMove > 0 || startedFromSetupPosition) {
12318 char *fen = PositionToFEN(backwardMostMove, NULL);
12319 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12320 fprintf(f, "\n{--------------\n");
12321 PrintPosition(f, backwardMostMove);
12322 fprintf(f, "--------------}\n");
12326 /* [AS] Out of book annotation */
12327 if( appData.saveOutOfBookInfo ) {
12330 GetOutOfBookInfo( buf );
12332 if( buf[0] != '\0' ) {
12333 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12340 i = backwardMostMove;
12344 while (i < forwardMostMove) {
12345 /* Print comments preceding this move */
12346 if (commentList[i] != NULL) {
12347 if (linelen > 0) fprintf(f, "\n");
12348 fprintf(f, "%s", commentList[i]);
12353 /* Format move number */
12355 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12358 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12360 numtext[0] = NULLCHAR;
12362 numlen = strlen(numtext);
12365 /* Print move number */
12366 blank = linelen > 0 && numlen > 0;
12367 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12376 fprintf(f, "%s", numtext);
12380 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12381 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12384 blank = linelen > 0 && movelen > 0;
12385 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12394 fprintf(f, "%s", move_buffer);
12395 linelen += movelen;
12397 /* [AS] Add PV info if present */
12398 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12399 /* [HGM] add time */
12400 char buf[MSG_SIZ]; int seconds;
12402 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12408 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12411 seconds = (seconds + 4)/10; // round to full seconds
12413 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12415 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12418 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12419 pvInfoList[i].score >= 0 ? "+" : "",
12420 pvInfoList[i].score / 100.0,
12421 pvInfoList[i].depth,
12424 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12426 /* Print score/depth */
12427 blank = linelen > 0 && movelen > 0;
12428 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12437 fprintf(f, "%s", move_buffer);
12438 linelen += movelen;
12444 /* Start a new line */
12445 if (linelen > 0) fprintf(f, "\n");
12447 /* Print comments after last move */
12448 if (commentList[i] != NULL) {
12449 fprintf(f, "%s\n", commentList[i]);
12453 if (gameInfo.resultDetails != NULL &&
12454 gameInfo.resultDetails[0] != NULLCHAR) {
12455 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12456 PGNResult(gameInfo.result));
12458 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12462 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12466 /* Save game in old style and close the file */
12468 SaveGameOldStyle(f)
12474 tm = time((time_t *) NULL);
12476 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12479 if (backwardMostMove > 0 || startedFromSetupPosition) {
12480 fprintf(f, "\n[--------------\n");
12481 PrintPosition(f, backwardMostMove);
12482 fprintf(f, "--------------]\n");
12487 i = backwardMostMove;
12488 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12490 while (i < forwardMostMove) {
12491 if (commentList[i] != NULL) {
12492 fprintf(f, "[%s]\n", commentList[i]);
12495 if ((i % 2) == 1) {
12496 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12499 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12501 if (commentList[i] != NULL) {
12505 if (i >= forwardMostMove) {
12509 fprintf(f, "%s\n", parseList[i]);
12514 if (commentList[i] != NULL) {
12515 fprintf(f, "[%s]\n", commentList[i]);
12518 /* This isn't really the old style, but it's close enough */
12519 if (gameInfo.resultDetails != NULL &&
12520 gameInfo.resultDetails[0] != NULLCHAR) {
12521 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12522 gameInfo.resultDetails);
12524 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12531 /* Save the current game to open file f and close the file */
12533 SaveGame(f, dummy, dummy2)
12538 if (gameMode == EditPosition) EditPositionDone(TRUE);
12539 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12540 if (appData.oldSaveStyle)
12541 return SaveGameOldStyle(f);
12543 return SaveGamePGN(f);
12546 /* Save the current position to the given file */
12548 SavePositionToFile(filename)
12554 if (strcmp(filename, "-") == 0) {
12555 return SavePosition(stdout, 0, NULL);
12557 f = fopen(filename, "a");
12559 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12560 DisplayError(buf, errno);
12563 safeStrCpy(buf, lastMsg, MSG_SIZ);
12564 DisplayMessage(_("Waiting for access to save file"), "");
12565 flock(fileno(f), LOCK_EX); // [HGM] lock
12566 DisplayMessage(_("Saving position"), "");
12567 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12568 SavePosition(f, 0, NULL);
12569 DisplayMessage(buf, "");
12575 /* Save the current position to the given open file and close the file */
12577 SavePosition(f, dummy, dummy2)
12585 if (gameMode == EditPosition) EditPositionDone(TRUE);
12586 if (appData.oldSaveStyle) {
12587 tm = time((time_t *) NULL);
12589 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12591 fprintf(f, "[--------------\n");
12592 PrintPosition(f, currentMove);
12593 fprintf(f, "--------------]\n");
12595 fen = PositionToFEN(currentMove, NULL);
12596 fprintf(f, "%s\n", fen);
12604 ReloadCmailMsgEvent(unregister)
12608 static char *inFilename = NULL;
12609 static char *outFilename;
12611 struct stat inbuf, outbuf;
12614 /* Any registered moves are unregistered if unregister is set, */
12615 /* i.e. invoked by the signal handler */
12617 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12618 cmailMoveRegistered[i] = FALSE;
12619 if (cmailCommentList[i] != NULL) {
12620 free(cmailCommentList[i]);
12621 cmailCommentList[i] = NULL;
12624 nCmailMovesRegistered = 0;
12627 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12628 cmailResult[i] = CMAIL_NOT_RESULT;
12632 if (inFilename == NULL) {
12633 /* Because the filenames are static they only get malloced once */
12634 /* and they never get freed */
12635 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12636 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12638 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12639 sprintf(outFilename, "%s.out", appData.cmailGameName);
12642 status = stat(outFilename, &outbuf);
12644 cmailMailedMove = FALSE;
12646 status = stat(inFilename, &inbuf);
12647 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12650 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12651 counts the games, notes how each one terminated, etc.
12653 It would be nice to remove this kludge and instead gather all
12654 the information while building the game list. (And to keep it
12655 in the game list nodes instead of having a bunch of fixed-size
12656 parallel arrays.) Note this will require getting each game's
12657 termination from the PGN tags, as the game list builder does
12658 not process the game moves. --mann
12660 cmailMsgLoaded = TRUE;
12661 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12663 /* Load first game in the file or popup game menu */
12664 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12666 #endif /* !WIN32 */
12674 char string[MSG_SIZ];
12676 if ( cmailMailedMove
12677 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12678 return TRUE; /* Allow free viewing */
12681 /* Unregister move to ensure that we don't leave RegisterMove */
12682 /* with the move registered when the conditions for registering no */
12684 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12685 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12686 nCmailMovesRegistered --;
12688 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12690 free(cmailCommentList[lastLoadGameNumber - 1]);
12691 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12695 if (cmailOldMove == -1) {
12696 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12700 if (currentMove > cmailOldMove + 1) {
12701 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12705 if (currentMove < cmailOldMove) {
12706 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12710 if (forwardMostMove > currentMove) {
12711 /* Silently truncate extra moves */
12715 if ( (currentMove == cmailOldMove + 1)
12716 || ( (currentMove == cmailOldMove)
12717 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12718 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12719 if (gameInfo.result != GameUnfinished) {
12720 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12723 if (commentList[currentMove] != NULL) {
12724 cmailCommentList[lastLoadGameNumber - 1]
12725 = StrSave(commentList[currentMove]);
12727 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12729 if (appData.debugMode)
12730 fprintf(debugFP, "Saving %s for game %d\n",
12731 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12733 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12735 f = fopen(string, "w");
12736 if (appData.oldSaveStyle) {
12737 SaveGameOldStyle(f); /* also closes the file */
12739 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12740 f = fopen(string, "w");
12741 SavePosition(f, 0, NULL); /* also closes the file */
12743 fprintf(f, "{--------------\n");
12744 PrintPosition(f, currentMove);
12745 fprintf(f, "--------------}\n\n");
12747 SaveGame(f, 0, NULL); /* also closes the file*/
12750 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12751 nCmailMovesRegistered ++;
12752 } else if (nCmailGames == 1) {
12753 DisplayError(_("You have not made a move yet"), 0);
12764 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12765 FILE *commandOutput;
12766 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12767 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12773 if (! cmailMsgLoaded) {
12774 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12778 if (nCmailGames == nCmailResults) {
12779 DisplayError(_("No unfinished games"), 0);
12783 #if CMAIL_PROHIBIT_REMAIL
12784 if (cmailMailedMove) {
12785 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);
12786 DisplayError(msg, 0);
12791 if (! (cmailMailedMove || RegisterMove())) return;
12793 if ( cmailMailedMove
12794 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12795 snprintf(string, MSG_SIZ, partCommandString,
12796 appData.debugMode ? " -v" : "", appData.cmailGameName);
12797 commandOutput = popen(string, "r");
12799 if (commandOutput == NULL) {
12800 DisplayError(_("Failed to invoke cmail"), 0);
12802 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12803 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12805 if (nBuffers > 1) {
12806 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12807 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12808 nBytes = MSG_SIZ - 1;
12810 (void) memcpy(msg, buffer, nBytes);
12812 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12814 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12815 cmailMailedMove = TRUE; /* Prevent >1 moves */
12818 for (i = 0; i < nCmailGames; i ++) {
12819 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12824 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12826 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12828 appData.cmailGameName,
12830 LoadGameFromFile(buffer, 1, buffer, FALSE);
12831 cmailMsgLoaded = FALSE;
12835 DisplayInformation(msg);
12836 pclose(commandOutput);
12839 if ((*cmailMsg) != '\0') {
12840 DisplayInformation(cmailMsg);
12845 #endif /* !WIN32 */
12854 int prependComma = 0;
12856 char string[MSG_SIZ]; /* Space for game-list */
12859 if (!cmailMsgLoaded) return "";
12861 if (cmailMailedMove) {
12862 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12864 /* Create a list of games left */
12865 snprintf(string, MSG_SIZ, "[");
12866 for (i = 0; i < nCmailGames; i ++) {
12867 if (! ( cmailMoveRegistered[i]
12868 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12869 if (prependComma) {
12870 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12872 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12876 strcat(string, number);
12879 strcat(string, "]");
12881 if (nCmailMovesRegistered + nCmailResults == 0) {
12882 switch (nCmailGames) {
12884 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12888 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12892 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12897 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12899 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12904 if (nCmailResults == nCmailGames) {
12905 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12907 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12912 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12924 if (gameMode == Training)
12925 SetTrainingModeOff();
12928 cmailMsgLoaded = FALSE;
12929 if (appData.icsActive) {
12930 SendToICS(ics_prefix);
12931 SendToICS("refresh\n");
12941 /* Give up on clean exit */
12945 /* Keep trying for clean exit */
12949 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12951 if (telnetISR != NULL) {
12952 RemoveInputSource(telnetISR);
12954 if (icsPR != NoProc) {
12955 DestroyChildProcess(icsPR, TRUE);
12958 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12959 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12961 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12962 /* make sure this other one finishes before killing it! */
12963 if(endingGame) { int count = 0;
12964 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12965 while(endingGame && count++ < 10) DoSleep(1);
12966 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12969 /* Kill off chess programs */
12970 if (first.pr != NoProc) {
12973 DoSleep( appData.delayBeforeQuit );
12974 SendToProgram("quit\n", &first);
12975 DoSleep( appData.delayAfterQuit );
12976 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12978 if (second.pr != NoProc) {
12979 DoSleep( appData.delayBeforeQuit );
12980 SendToProgram("quit\n", &second);
12981 DoSleep( appData.delayAfterQuit );
12982 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12984 if (first.isr != NULL) {
12985 RemoveInputSource(first.isr);
12987 if (second.isr != NULL) {
12988 RemoveInputSource(second.isr);
12991 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12992 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12994 ShutDownFrontEnd();
13001 if (appData.debugMode)
13002 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13006 if (gameMode == MachinePlaysWhite ||
13007 gameMode == MachinePlaysBlack) {
13010 DisplayBothClocks();
13012 if (gameMode == PlayFromGameFile) {
13013 if (appData.timeDelay >= 0)
13014 AutoPlayGameLoop();
13015 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13016 Reset(FALSE, TRUE);
13017 SendToICS(ics_prefix);
13018 SendToICS("refresh\n");
13019 } else if (currentMove < forwardMostMove) {
13020 ForwardInner(forwardMostMove);
13022 pauseExamInvalid = FALSE;
13024 switch (gameMode) {
13028 pauseExamForwardMostMove = forwardMostMove;
13029 pauseExamInvalid = FALSE;
13032 case IcsPlayingWhite:
13033 case IcsPlayingBlack:
13037 case PlayFromGameFile:
13038 (void) StopLoadGameTimer();
13042 case BeginningOfGame:
13043 if (appData.icsActive) return;
13044 /* else fall through */
13045 case MachinePlaysWhite:
13046 case MachinePlaysBlack:
13047 case TwoMachinesPlay:
13048 if (forwardMostMove == 0)
13049 return; /* don't pause if no one has moved */
13050 if ((gameMode == MachinePlaysWhite &&
13051 !WhiteOnMove(forwardMostMove)) ||
13052 (gameMode == MachinePlaysBlack &&
13053 WhiteOnMove(forwardMostMove))) {
13066 char title[MSG_SIZ];
13068 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13069 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13071 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13072 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13073 parseList[currentMove - 1]);
13076 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13083 char *tags = PGNTags(&gameInfo);
13085 EditTagsPopUp(tags, NULL);
13092 if (appData.noChessProgram || gameMode == AnalyzeMode)
13095 if (gameMode != AnalyzeFile) {
13096 if (!appData.icsEngineAnalyze) {
13098 if (gameMode != EditGame) return;
13100 ResurrectChessProgram();
13101 SendToProgram("analyze\n", &first);
13102 first.analyzing = TRUE;
13103 /*first.maybeThinking = TRUE;*/
13104 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13105 EngineOutputPopUp();
13107 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13112 StartAnalysisClock();
13113 GetTimeMark(&lastNodeCountTime);
13120 if (appData.noChessProgram || gameMode == AnalyzeFile)
13123 if (gameMode != AnalyzeMode) {
13125 if (gameMode != EditGame) return;
13126 ResurrectChessProgram();
13127 SendToProgram("analyze\n", &first);
13128 first.analyzing = TRUE;
13129 /*first.maybeThinking = TRUE;*/
13130 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13131 EngineOutputPopUp();
13133 gameMode = AnalyzeFile;
13138 StartAnalysisClock();
13139 GetTimeMark(&lastNodeCountTime);
13141 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13145 MachineWhiteEvent()
13148 char *bookHit = NULL;
13150 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13154 if (gameMode == PlayFromGameFile ||
13155 gameMode == TwoMachinesPlay ||
13156 gameMode == Training ||
13157 gameMode == AnalyzeMode ||
13158 gameMode == EndOfGame)
13161 if (gameMode == EditPosition)
13162 EditPositionDone(TRUE);
13164 if (!WhiteOnMove(currentMove)) {
13165 DisplayError(_("It is not White's turn"), 0);
13169 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13172 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13173 gameMode == AnalyzeFile)
13176 ResurrectChessProgram(); /* in case it isn't running */
13177 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13178 gameMode = MachinePlaysWhite;
13181 gameMode = MachinePlaysWhite;
13185 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13187 if (first.sendName) {
13188 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13189 SendToProgram(buf, &first);
13191 if (first.sendTime) {
13192 if (first.useColors) {
13193 SendToProgram("black\n", &first); /*gnu kludge*/
13195 SendTimeRemaining(&first, TRUE);
13197 if (first.useColors) {
13198 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13200 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13201 SetMachineThinkingEnables();
13202 first.maybeThinking = TRUE;
13206 if (appData.autoFlipView && !flipView) {
13207 flipView = !flipView;
13208 DrawPosition(FALSE, NULL);
13209 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13212 if(bookHit) { // [HGM] book: simulate book reply
13213 static char bookMove[MSG_SIZ]; // a bit generous?
13215 programStats.nodes = programStats.depth = programStats.time =
13216 programStats.score = programStats.got_only_move = 0;
13217 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13219 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13220 strcat(bookMove, bookHit);
13221 HandleMachineMove(bookMove, &first);
13226 MachineBlackEvent()
13229 char *bookHit = NULL;
13231 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13235 if (gameMode == PlayFromGameFile ||
13236 gameMode == TwoMachinesPlay ||
13237 gameMode == Training ||
13238 gameMode == AnalyzeMode ||
13239 gameMode == EndOfGame)
13242 if (gameMode == EditPosition)
13243 EditPositionDone(TRUE);
13245 if (WhiteOnMove(currentMove)) {
13246 DisplayError(_("It is not Black's turn"), 0);
13250 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13253 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13254 gameMode == AnalyzeFile)
13257 ResurrectChessProgram(); /* in case it isn't running */
13258 gameMode = MachinePlaysBlack;
13262 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13264 if (first.sendName) {
13265 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13266 SendToProgram(buf, &first);
13268 if (first.sendTime) {
13269 if (first.useColors) {
13270 SendToProgram("white\n", &first); /*gnu kludge*/
13272 SendTimeRemaining(&first, FALSE);
13274 if (first.useColors) {
13275 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13277 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13278 SetMachineThinkingEnables();
13279 first.maybeThinking = TRUE;
13282 if (appData.autoFlipView && flipView) {
13283 flipView = !flipView;
13284 DrawPosition(FALSE, NULL);
13285 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13287 if(bookHit) { // [HGM] book: simulate book reply
13288 static char bookMove[MSG_SIZ]; // a bit generous?
13290 programStats.nodes = programStats.depth = programStats.time =
13291 programStats.score = programStats.got_only_move = 0;
13292 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13294 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13295 strcat(bookMove, bookHit);
13296 HandleMachineMove(bookMove, &first);
13302 DisplayTwoMachinesTitle()
13305 if (appData.matchGames > 0) {
13306 if(appData.tourneyFile[0]) {
13307 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13308 gameInfo.white, gameInfo.black,
13309 nextGame+1, appData.matchGames+1,
13310 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13312 if (first.twoMachinesColor[0] == 'w') {
13313 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13314 gameInfo.white, gameInfo.black,
13315 first.matchWins, second.matchWins,
13316 matchGame - 1 - (first.matchWins + second.matchWins));
13318 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13319 gameInfo.white, gameInfo.black,
13320 second.matchWins, first.matchWins,
13321 matchGame - 1 - (first.matchWins + second.matchWins));
13324 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13330 SettingsMenuIfReady()
13332 if (second.lastPing != second.lastPong) {
13333 DisplayMessage("", _("Waiting for second chess program"));
13334 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13338 DisplayMessage("", "");
13339 SettingsPopUp(&second);
13343 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13346 if (cps->pr == NoProc) {
13347 StartChessProgram(cps);
13348 if (cps->protocolVersion == 1) {
13351 /* kludge: allow timeout for initial "feature" command */
13353 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13354 DisplayMessage("", buf);
13355 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13363 TwoMachinesEvent P((void))
13367 ChessProgramState *onmove;
13368 char *bookHit = NULL;
13369 static int stalling = 0;
13373 if (appData.noChessProgram) return;
13375 switch (gameMode) {
13376 case TwoMachinesPlay:
13378 case MachinePlaysWhite:
13379 case MachinePlaysBlack:
13380 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13381 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13385 case BeginningOfGame:
13386 case PlayFromGameFile:
13389 if (gameMode != EditGame) return;
13392 EditPositionDone(TRUE);
13403 // forwardMostMove = currentMove;
13404 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13406 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13408 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13409 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13410 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13414 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13415 SendToProgram("force\n", &second);
13417 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13420 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13421 if(appData.matchPause>10000 || appData.matchPause<10)
13422 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13423 wait = SubtractTimeMarks(&now, &pauseStart);
13424 if(wait < appData.matchPause) {
13425 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13429 DisplayMessage("", "");
13430 if (startedFromSetupPosition) {
13431 SendBoard(&second, backwardMostMove);
13432 if (appData.debugMode) {
13433 fprintf(debugFP, "Two Machines\n");
13436 for (i = backwardMostMove; i < forwardMostMove; i++) {
13437 SendMoveToProgram(i, &second);
13440 gameMode = TwoMachinesPlay;
13442 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13444 DisplayTwoMachinesTitle();
13446 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13451 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13452 SendToProgram(first.computerString, &first);
13453 if (first.sendName) {
13454 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13455 SendToProgram(buf, &first);
13457 SendToProgram(second.computerString, &second);
13458 if (second.sendName) {
13459 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13460 SendToProgram(buf, &second);
13464 if (!first.sendTime || !second.sendTime) {
13465 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13466 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13468 if (onmove->sendTime) {
13469 if (onmove->useColors) {
13470 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13472 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13474 if (onmove->useColors) {
13475 SendToProgram(onmove->twoMachinesColor, onmove);
13477 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13478 // SendToProgram("go\n", onmove);
13479 onmove->maybeThinking = TRUE;
13480 SetMachineThinkingEnables();
13484 if(bookHit) { // [HGM] book: simulate book reply
13485 static char bookMove[MSG_SIZ]; // a bit generous?
13487 programStats.nodes = programStats.depth = programStats.time =
13488 programStats.score = programStats.got_only_move = 0;
13489 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13491 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13492 strcat(bookMove, bookHit);
13493 savedMessage = bookMove; // args for deferred call
13494 savedState = onmove;
13495 ScheduleDelayedEvent(DeferredBookMove, 1);
13502 if (gameMode == Training) {
13503 SetTrainingModeOff();
13504 gameMode = PlayFromGameFile;
13505 DisplayMessage("", _("Training mode off"));
13507 gameMode = Training;
13508 animateTraining = appData.animate;
13510 /* make sure we are not already at the end of the game */
13511 if (currentMove < forwardMostMove) {
13512 SetTrainingModeOn();
13513 DisplayMessage("", _("Training mode on"));
13515 gameMode = PlayFromGameFile;
13516 DisplayError(_("Already at end of game"), 0);
13525 if (!appData.icsActive) return;
13526 switch (gameMode) {
13527 case IcsPlayingWhite:
13528 case IcsPlayingBlack:
13531 case BeginningOfGame:
13539 EditPositionDone(TRUE);
13552 gameMode = IcsIdle;
13563 switch (gameMode) {
13565 SetTrainingModeOff();
13567 case MachinePlaysWhite:
13568 case MachinePlaysBlack:
13569 case BeginningOfGame:
13570 SendToProgram("force\n", &first);
13571 SetUserThinkingEnables();
13573 case PlayFromGameFile:
13574 (void) StopLoadGameTimer();
13575 if (gameFileFP != NULL) {
13580 EditPositionDone(TRUE);
13585 SendToProgram("force\n", &first);
13587 case TwoMachinesPlay:
13588 GameEnds(EndOfFile, NULL, GE_PLAYER);
13589 ResurrectChessProgram();
13590 SetUserThinkingEnables();
13593 ResurrectChessProgram();
13595 case IcsPlayingBlack:
13596 case IcsPlayingWhite:
13597 DisplayError(_("Warning: You are still playing a game"), 0);
13600 DisplayError(_("Warning: You are still observing a game"), 0);
13603 DisplayError(_("Warning: You are still examining a game"), 0);
13614 first.offeredDraw = second.offeredDraw = 0;
13616 if (gameMode == PlayFromGameFile) {
13617 whiteTimeRemaining = timeRemaining[0][currentMove];
13618 blackTimeRemaining = timeRemaining[1][currentMove];
13622 if (gameMode == MachinePlaysWhite ||
13623 gameMode == MachinePlaysBlack ||
13624 gameMode == TwoMachinesPlay ||
13625 gameMode == EndOfGame) {
13626 i = forwardMostMove;
13627 while (i > currentMove) {
13628 SendToProgram("undo\n", &first);
13631 if(!adjustedClock) {
13632 whiteTimeRemaining = timeRemaining[0][currentMove];
13633 blackTimeRemaining = timeRemaining[1][currentMove];
13634 DisplayBothClocks();
13636 if (whiteFlag || blackFlag) {
13637 whiteFlag = blackFlag = 0;
13642 gameMode = EditGame;
13649 EditPositionEvent()
13651 if (gameMode == EditPosition) {
13657 if (gameMode != EditGame) return;
13659 gameMode = EditPosition;
13662 if (currentMove > 0)
13663 CopyBoard(boards[0], boards[currentMove]);
13665 blackPlaysFirst = !WhiteOnMove(currentMove);
13667 currentMove = forwardMostMove = backwardMostMove = 0;
13668 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13675 /* [DM] icsEngineAnalyze - possible call from other functions */
13676 if (appData.icsEngineAnalyze) {
13677 appData.icsEngineAnalyze = FALSE;
13679 DisplayMessage("",_("Close ICS engine analyze..."));
13681 if (first.analysisSupport && first.analyzing) {
13682 SendToProgram("exit\n", &first);
13683 first.analyzing = FALSE;
13685 thinkOutput[0] = NULLCHAR;
13689 EditPositionDone(Boolean fakeRights)
13691 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13693 startedFromSetupPosition = TRUE;
13694 InitChessProgram(&first, FALSE);
13695 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13696 boards[0][EP_STATUS] = EP_NONE;
13697 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13698 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13699 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13700 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13701 } else boards[0][CASTLING][2] = NoRights;
13702 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13703 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13704 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13705 } else boards[0][CASTLING][5] = NoRights;
13707 SendToProgram("force\n", &first);
13708 if (blackPlaysFirst) {
13709 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13710 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13711 currentMove = forwardMostMove = backwardMostMove = 1;
13712 CopyBoard(boards[1], boards[0]);
13714 currentMove = forwardMostMove = backwardMostMove = 0;
13716 SendBoard(&first, forwardMostMove);
13717 if (appData.debugMode) {
13718 fprintf(debugFP, "EditPosDone\n");
13721 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13722 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13723 gameMode = EditGame;
13725 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13726 ClearHighlights(); /* [AS] */
13729 /* Pause for `ms' milliseconds */
13730 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13740 } while (SubtractTimeMarks(&m2, &m1) < ms);
13743 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13745 SendMultiLineToICS(buf)
13748 char temp[MSG_SIZ+1], *p;
13755 strncpy(temp, buf, len);
13760 if (*p == '\n' || *p == '\r')
13765 strcat(temp, "\n");
13767 SendToPlayer(temp, strlen(temp));
13771 SetWhiteToPlayEvent()
13773 if (gameMode == EditPosition) {
13774 blackPlaysFirst = FALSE;
13775 DisplayBothClocks(); /* works because currentMove is 0 */
13776 } else if (gameMode == IcsExamining) {
13777 SendToICS(ics_prefix);
13778 SendToICS("tomove white\n");
13783 SetBlackToPlayEvent()
13785 if (gameMode == EditPosition) {
13786 blackPlaysFirst = TRUE;
13787 currentMove = 1; /* kludge */
13788 DisplayBothClocks();
13790 } else if (gameMode == IcsExamining) {
13791 SendToICS(ics_prefix);
13792 SendToICS("tomove black\n");
13797 EditPositionMenuEvent(selection, x, y)
13798 ChessSquare selection;
13802 ChessSquare piece = boards[0][y][x];
13804 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13806 switch (selection) {
13808 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13809 SendToICS(ics_prefix);
13810 SendToICS("bsetup clear\n");
13811 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13812 SendToICS(ics_prefix);
13813 SendToICS("clearboard\n");
13815 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13816 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13817 for (y = 0; y < BOARD_HEIGHT; y++) {
13818 if (gameMode == IcsExamining) {
13819 if (boards[currentMove][y][x] != EmptySquare) {
13820 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13825 boards[0][y][x] = p;
13830 if (gameMode == EditPosition) {
13831 DrawPosition(FALSE, boards[0]);
13836 SetWhiteToPlayEvent();
13840 SetBlackToPlayEvent();
13844 if (gameMode == IcsExamining) {
13845 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13846 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13849 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13850 if(x == BOARD_LEFT-2) {
13851 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13852 boards[0][y][1] = 0;
13854 if(x == BOARD_RGHT+1) {
13855 if(y >= gameInfo.holdingsSize) break;
13856 boards[0][y][BOARD_WIDTH-2] = 0;
13859 boards[0][y][x] = EmptySquare;
13860 DrawPosition(FALSE, boards[0]);
13865 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13866 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13867 selection = (ChessSquare) (PROMOTED piece);
13868 } else if(piece == EmptySquare) selection = WhiteSilver;
13869 else selection = (ChessSquare)((int)piece - 1);
13873 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13874 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13875 selection = (ChessSquare) (DEMOTED piece);
13876 } else if(piece == EmptySquare) selection = BlackSilver;
13877 else selection = (ChessSquare)((int)piece + 1);
13882 if(gameInfo.variant == VariantShatranj ||
13883 gameInfo.variant == VariantXiangqi ||
13884 gameInfo.variant == VariantCourier ||
13885 gameInfo.variant == VariantMakruk )
13886 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13891 if(gameInfo.variant == VariantXiangqi)
13892 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13893 if(gameInfo.variant == VariantKnightmate)
13894 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13897 if (gameMode == IcsExamining) {
13898 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13899 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13900 PieceToChar(selection), AAA + x, ONE + y);
13903 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13905 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13906 n = PieceToNumber(selection - BlackPawn);
13907 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13908 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13909 boards[0][BOARD_HEIGHT-1-n][1]++;
13911 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13912 n = PieceToNumber(selection);
13913 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13914 boards[0][n][BOARD_WIDTH-1] = selection;
13915 boards[0][n][BOARD_WIDTH-2]++;
13918 boards[0][y][x] = selection;
13919 DrawPosition(TRUE, boards[0]);
13927 DropMenuEvent(selection, x, y)
13928 ChessSquare selection;
13931 ChessMove moveType;
13933 switch (gameMode) {
13934 case IcsPlayingWhite:
13935 case MachinePlaysBlack:
13936 if (!WhiteOnMove(currentMove)) {
13937 DisplayMoveError(_("It is Black's turn"));
13940 moveType = WhiteDrop;
13942 case IcsPlayingBlack:
13943 case MachinePlaysWhite:
13944 if (WhiteOnMove(currentMove)) {
13945 DisplayMoveError(_("It is White's turn"));
13948 moveType = BlackDrop;
13951 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13957 if (moveType == BlackDrop && selection < BlackPawn) {
13958 selection = (ChessSquare) ((int) selection
13959 + (int) BlackPawn - (int) WhitePawn);
13961 if (boards[currentMove][y][x] != EmptySquare) {
13962 DisplayMoveError(_("That square is occupied"));
13966 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13972 /* Accept a pending offer of any kind from opponent */
13974 if (appData.icsActive) {
13975 SendToICS(ics_prefix);
13976 SendToICS("accept\n");
13977 } else if (cmailMsgLoaded) {
13978 if (currentMove == cmailOldMove &&
13979 commentList[cmailOldMove] != NULL &&
13980 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13981 "Black offers a draw" : "White offers a draw")) {
13983 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13984 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13986 DisplayError(_("There is no pending offer on this move"), 0);
13987 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13990 /* Not used for offers from chess program */
13997 /* Decline a pending offer of any kind from opponent */
13999 if (appData.icsActive) {
14000 SendToICS(ics_prefix);
14001 SendToICS("decline\n");
14002 } else if (cmailMsgLoaded) {
14003 if (currentMove == cmailOldMove &&
14004 commentList[cmailOldMove] != NULL &&
14005 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14006 "Black offers a draw" : "White offers a draw")) {
14008 AppendComment(cmailOldMove, "Draw declined", TRUE);
14009 DisplayComment(cmailOldMove - 1, "Draw declined");
14012 DisplayError(_("There is no pending offer on this move"), 0);
14015 /* Not used for offers from chess program */
14022 /* Issue ICS rematch command */
14023 if (appData.icsActive) {
14024 SendToICS(ics_prefix);
14025 SendToICS("rematch\n");
14032 /* Call your opponent's flag (claim a win on time) */
14033 if (appData.icsActive) {
14034 SendToICS(ics_prefix);
14035 SendToICS("flag\n");
14037 switch (gameMode) {
14040 case MachinePlaysWhite:
14043 GameEnds(GameIsDrawn, "Both players ran out of time",
14046 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14048 DisplayError(_("Your opponent is not out of time"), 0);
14051 case MachinePlaysBlack:
14054 GameEnds(GameIsDrawn, "Both players ran out of time",
14057 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14059 DisplayError(_("Your opponent is not out of time"), 0);
14067 ClockClick(int which)
14068 { // [HGM] code moved to back-end from winboard.c
14069 if(which) { // black clock
14070 if (gameMode == EditPosition || gameMode == IcsExamining) {
14071 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14072 SetBlackToPlayEvent();
14073 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14074 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14075 } else if (shiftKey) {
14076 AdjustClock(which, -1);
14077 } else if (gameMode == IcsPlayingWhite ||
14078 gameMode == MachinePlaysBlack) {
14081 } else { // white clock
14082 if (gameMode == EditPosition || gameMode == IcsExamining) {
14083 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14084 SetWhiteToPlayEvent();
14085 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14086 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14087 } else if (shiftKey) {
14088 AdjustClock(which, -1);
14089 } else if (gameMode == IcsPlayingBlack ||
14090 gameMode == MachinePlaysWhite) {
14099 /* Offer draw or accept pending draw offer from opponent */
14101 if (appData.icsActive) {
14102 /* Note: tournament rules require draw offers to be
14103 made after you make your move but before you punch
14104 your clock. Currently ICS doesn't let you do that;
14105 instead, you immediately punch your clock after making
14106 a move, but you can offer a draw at any time. */
14108 SendToICS(ics_prefix);
14109 SendToICS("draw\n");
14110 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14111 } else if (cmailMsgLoaded) {
14112 if (currentMove == cmailOldMove &&
14113 commentList[cmailOldMove] != NULL &&
14114 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14115 "Black offers a draw" : "White offers a draw")) {
14116 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14117 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14118 } else if (currentMove == cmailOldMove + 1) {
14119 char *offer = WhiteOnMove(cmailOldMove) ?
14120 "White offers a draw" : "Black offers a draw";
14121 AppendComment(currentMove, offer, TRUE);
14122 DisplayComment(currentMove - 1, offer);
14123 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14125 DisplayError(_("You must make your move before offering a draw"), 0);
14126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14128 } else if (first.offeredDraw) {
14129 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14131 if (first.sendDrawOffers) {
14132 SendToProgram("draw\n", &first);
14133 userOfferedDraw = TRUE;
14141 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14143 if (appData.icsActive) {
14144 SendToICS(ics_prefix);
14145 SendToICS("adjourn\n");
14147 /* Currently GNU Chess doesn't offer or accept Adjourns */
14155 /* Offer Abort or accept pending Abort offer from opponent */
14157 if (appData.icsActive) {
14158 SendToICS(ics_prefix);
14159 SendToICS("abort\n");
14161 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14168 /* Resign. You can do this even if it's not your turn. */
14170 if (appData.icsActive) {
14171 SendToICS(ics_prefix);
14172 SendToICS("resign\n");
14174 switch (gameMode) {
14175 case MachinePlaysWhite:
14176 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14178 case MachinePlaysBlack:
14179 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14182 if (cmailMsgLoaded) {
14184 if (WhiteOnMove(cmailOldMove)) {
14185 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14187 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14189 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14200 StopObservingEvent()
14202 /* Stop observing current games */
14203 SendToICS(ics_prefix);
14204 SendToICS("unobserve\n");
14208 StopExaminingEvent()
14210 /* Stop observing current game */
14211 SendToICS(ics_prefix);
14212 SendToICS("unexamine\n");
14216 ForwardInner(target)
14221 if (appData.debugMode)
14222 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14223 target, currentMove, forwardMostMove);
14225 if (gameMode == EditPosition)
14228 if (gameMode == PlayFromGameFile && !pausing)
14231 if (gameMode == IcsExamining && pausing)
14232 limit = pauseExamForwardMostMove;
14234 limit = forwardMostMove;
14236 if (target > limit) target = limit;
14238 if (target > 0 && moveList[target - 1][0]) {
14239 int fromX, fromY, toX, toY;
14240 toX = moveList[target - 1][2] - AAA;
14241 toY = moveList[target - 1][3] - ONE;
14242 if (moveList[target - 1][1] == '@') {
14243 if (appData.highlightLastMove) {
14244 SetHighlights(-1, -1, toX, toY);
14247 fromX = moveList[target - 1][0] - AAA;
14248 fromY = moveList[target - 1][1] - ONE;
14249 if (target == currentMove + 1) {
14250 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14252 if (appData.highlightLastMove) {
14253 SetHighlights(fromX, fromY, toX, toY);
14257 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14258 gameMode == Training || gameMode == PlayFromGameFile ||
14259 gameMode == AnalyzeFile) {
14260 while (currentMove < target) {
14261 SendMoveToProgram(currentMove++, &first);
14264 currentMove = target;
14267 if (gameMode == EditGame || gameMode == EndOfGame) {
14268 whiteTimeRemaining = timeRemaining[0][currentMove];
14269 blackTimeRemaining = timeRemaining[1][currentMove];
14271 DisplayBothClocks();
14272 DisplayMove(currentMove - 1);
14273 DrawPosition(FALSE, boards[currentMove]);
14274 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14275 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14276 DisplayComment(currentMove - 1, commentList[currentMove]);
14284 if (gameMode == IcsExamining && !pausing) {
14285 SendToICS(ics_prefix);
14286 SendToICS("forward\n");
14288 ForwardInner(currentMove + 1);
14295 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14296 /* to optimze, we temporarily turn off analysis mode while we feed
14297 * the remaining moves to the engine. Otherwise we get analysis output
14300 if (first.analysisSupport) {
14301 SendToProgram("exit\nforce\n", &first);
14302 first.analyzing = FALSE;
14306 if (gameMode == IcsExamining && !pausing) {
14307 SendToICS(ics_prefix);
14308 SendToICS("forward 999999\n");
14310 ForwardInner(forwardMostMove);
14313 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14314 /* we have fed all the moves, so reactivate analysis mode */
14315 SendToProgram("analyze\n", &first);
14316 first.analyzing = TRUE;
14317 /*first.maybeThinking = TRUE;*/
14318 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14323 BackwardInner(target)
14326 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14328 if (appData.debugMode)
14329 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14330 target, currentMove, forwardMostMove);
14332 if (gameMode == EditPosition) return;
14333 if (currentMove <= backwardMostMove) {
14335 DrawPosition(full_redraw, boards[currentMove]);
14338 if (gameMode == PlayFromGameFile && !pausing)
14341 if (moveList[target][0]) {
14342 int fromX, fromY, toX, toY;
14343 toX = moveList[target][2] - AAA;
14344 toY = moveList[target][3] - ONE;
14345 if (moveList[target][1] == '@') {
14346 if (appData.highlightLastMove) {
14347 SetHighlights(-1, -1, toX, toY);
14350 fromX = moveList[target][0] - AAA;
14351 fromY = moveList[target][1] - ONE;
14352 if (target == currentMove - 1) {
14353 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14355 if (appData.highlightLastMove) {
14356 SetHighlights(fromX, fromY, toX, toY);
14360 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14361 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14362 while (currentMove > target) {
14363 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14364 // null move cannot be undone. Reload program with move history before it.
14366 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14367 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14369 SendBoard(&first, i);
14370 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14373 SendToProgram("undo\n", &first);
14377 currentMove = target;
14380 if (gameMode == EditGame || gameMode == EndOfGame) {
14381 whiteTimeRemaining = timeRemaining[0][currentMove];
14382 blackTimeRemaining = timeRemaining[1][currentMove];
14384 DisplayBothClocks();
14385 DisplayMove(currentMove - 1);
14386 DrawPosition(full_redraw, boards[currentMove]);
14387 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14388 // [HGM] PV info: routine tests if comment empty
14389 DisplayComment(currentMove - 1, commentList[currentMove]);
14395 if (gameMode == IcsExamining && !pausing) {
14396 SendToICS(ics_prefix);
14397 SendToICS("backward\n");
14399 BackwardInner(currentMove - 1);
14406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14407 /* to optimize, we temporarily turn off analysis mode while we undo
14408 * all the moves. Otherwise we get analysis output after each undo.
14410 if (first.analysisSupport) {
14411 SendToProgram("exit\nforce\n", &first);
14412 first.analyzing = FALSE;
14416 if (gameMode == IcsExamining && !pausing) {
14417 SendToICS(ics_prefix);
14418 SendToICS("backward 999999\n");
14420 BackwardInner(backwardMostMove);
14423 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14424 /* we have fed all the moves, so reactivate analysis mode */
14425 SendToProgram("analyze\n", &first);
14426 first.analyzing = TRUE;
14427 /*first.maybeThinking = TRUE;*/
14428 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14435 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14436 if (to >= forwardMostMove) to = forwardMostMove;
14437 if (to <= backwardMostMove) to = backwardMostMove;
14438 if (to < currentMove) {
14446 RevertEvent(Boolean annotate)
14448 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14451 if (gameMode != IcsExamining) {
14452 DisplayError(_("You are not examining a game"), 0);
14456 DisplayError(_("You can't revert while pausing"), 0);
14459 SendToICS(ics_prefix);
14460 SendToICS("revert\n");
14466 switch (gameMode) {
14467 case MachinePlaysWhite:
14468 case MachinePlaysBlack:
14469 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14470 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14473 if (forwardMostMove < 2) return;
14474 currentMove = forwardMostMove = forwardMostMove - 2;
14475 whiteTimeRemaining = timeRemaining[0][currentMove];
14476 blackTimeRemaining = timeRemaining[1][currentMove];
14477 DisplayBothClocks();
14478 DisplayMove(currentMove - 1);
14479 ClearHighlights();/*!! could figure this out*/
14480 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14481 SendToProgram("remove\n", &first);
14482 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14485 case BeginningOfGame:
14489 case IcsPlayingWhite:
14490 case IcsPlayingBlack:
14491 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14492 SendToICS(ics_prefix);
14493 SendToICS("takeback 2\n");
14495 SendToICS(ics_prefix);
14496 SendToICS("takeback 1\n");
14505 ChessProgramState *cps;
14507 switch (gameMode) {
14508 case MachinePlaysWhite:
14509 if (!WhiteOnMove(forwardMostMove)) {
14510 DisplayError(_("It is your turn"), 0);
14515 case MachinePlaysBlack:
14516 if (WhiteOnMove(forwardMostMove)) {
14517 DisplayError(_("It is your turn"), 0);
14522 case TwoMachinesPlay:
14523 if (WhiteOnMove(forwardMostMove) ==
14524 (first.twoMachinesColor[0] == 'w')) {
14530 case BeginningOfGame:
14534 SendToProgram("?\n", cps);
14538 TruncateGameEvent()
14541 if (gameMode != EditGame) return;
14548 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14549 if (forwardMostMove > currentMove) {
14550 if (gameInfo.resultDetails != NULL) {
14551 free(gameInfo.resultDetails);
14552 gameInfo.resultDetails = NULL;
14553 gameInfo.result = GameUnfinished;
14555 forwardMostMove = currentMove;
14556 HistorySet(parseList, backwardMostMove, forwardMostMove,
14564 if (appData.noChessProgram) return;
14565 switch (gameMode) {
14566 case MachinePlaysWhite:
14567 if (WhiteOnMove(forwardMostMove)) {
14568 DisplayError(_("Wait until your turn"), 0);
14572 case BeginningOfGame:
14573 case MachinePlaysBlack:
14574 if (!WhiteOnMove(forwardMostMove)) {
14575 DisplayError(_("Wait until your turn"), 0);
14580 DisplayError(_("No hint available"), 0);
14583 SendToProgram("hint\n", &first);
14584 hintRequested = TRUE;
14590 if (appData.noChessProgram) return;
14591 switch (gameMode) {
14592 case MachinePlaysWhite:
14593 if (WhiteOnMove(forwardMostMove)) {
14594 DisplayError(_("Wait until your turn"), 0);
14598 case BeginningOfGame:
14599 case MachinePlaysBlack:
14600 if (!WhiteOnMove(forwardMostMove)) {
14601 DisplayError(_("Wait until your turn"), 0);
14606 EditPositionDone(TRUE);
14608 case TwoMachinesPlay:
14613 SendToProgram("bk\n", &first);
14614 bookOutput[0] = NULLCHAR;
14615 bookRequested = TRUE;
14621 char *tags = PGNTags(&gameInfo);
14622 TagsPopUp(tags, CmailMsg());
14626 /* end button procedures */
14629 PrintPosition(fp, move)
14635 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14636 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14637 char c = PieceToChar(boards[move][i][j]);
14638 fputc(c == 'x' ? '.' : c, fp);
14639 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14642 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14643 fprintf(fp, "white to play\n");
14645 fprintf(fp, "black to play\n");
14652 if (gameInfo.white != NULL) {
14653 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14659 /* Find last component of program's own name, using some heuristics */
14661 TidyProgramName(prog, host, buf)
14662 char *prog, *host, buf[MSG_SIZ];
14665 int local = (strcmp(host, "localhost") == 0);
14666 while (!local && (p = strchr(prog, ';')) != NULL) {
14668 while (*p == ' ') p++;
14671 if (*prog == '"' || *prog == '\'') {
14672 q = strchr(prog + 1, *prog);
14674 q = strchr(prog, ' ');
14676 if (q == NULL) q = prog + strlen(prog);
14678 while (p >= prog && *p != '/' && *p != '\\') p--;
14680 if(p == prog && *p == '"') p++;
14681 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14682 memcpy(buf, p, q - p);
14683 buf[q - p] = NULLCHAR;
14691 TimeControlTagValue()
14694 if (!appData.clockMode) {
14695 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14696 } else if (movesPerSession > 0) {
14697 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14698 } else if (timeIncrement == 0) {
14699 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14701 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14703 return StrSave(buf);
14709 /* This routine is used only for certain modes */
14710 VariantClass v = gameInfo.variant;
14711 ChessMove r = GameUnfinished;
14714 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14715 r = gameInfo.result;
14716 p = gameInfo.resultDetails;
14717 gameInfo.resultDetails = NULL;
14719 ClearGameInfo(&gameInfo);
14720 gameInfo.variant = v;
14722 switch (gameMode) {
14723 case MachinePlaysWhite:
14724 gameInfo.event = StrSave( appData.pgnEventHeader );
14725 gameInfo.site = StrSave(HostName());
14726 gameInfo.date = PGNDate();
14727 gameInfo.round = StrSave("-");
14728 gameInfo.white = StrSave(first.tidy);
14729 gameInfo.black = StrSave(UserName());
14730 gameInfo.timeControl = TimeControlTagValue();
14733 case MachinePlaysBlack:
14734 gameInfo.event = StrSave( appData.pgnEventHeader );
14735 gameInfo.site = StrSave(HostName());
14736 gameInfo.date = PGNDate();
14737 gameInfo.round = StrSave("-");
14738 gameInfo.white = StrSave(UserName());
14739 gameInfo.black = StrSave(first.tidy);
14740 gameInfo.timeControl = TimeControlTagValue();
14743 case TwoMachinesPlay:
14744 gameInfo.event = StrSave( appData.pgnEventHeader );
14745 gameInfo.site = StrSave(HostName());
14746 gameInfo.date = PGNDate();
14749 snprintf(buf, MSG_SIZ, "%d", roundNr);
14750 gameInfo.round = StrSave(buf);
14752 gameInfo.round = StrSave("-");
14754 if (first.twoMachinesColor[0] == 'w') {
14755 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14756 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14758 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14759 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14761 gameInfo.timeControl = TimeControlTagValue();
14765 gameInfo.event = StrSave("Edited game");
14766 gameInfo.site = StrSave(HostName());
14767 gameInfo.date = PGNDate();
14768 gameInfo.round = StrSave("-");
14769 gameInfo.white = StrSave("-");
14770 gameInfo.black = StrSave("-");
14771 gameInfo.result = r;
14772 gameInfo.resultDetails = p;
14776 gameInfo.event = StrSave("Edited position");
14777 gameInfo.site = StrSave(HostName());
14778 gameInfo.date = PGNDate();
14779 gameInfo.round = StrSave("-");
14780 gameInfo.white = StrSave("-");
14781 gameInfo.black = StrSave("-");
14784 case IcsPlayingWhite:
14785 case IcsPlayingBlack:
14790 case PlayFromGameFile:
14791 gameInfo.event = StrSave("Game from non-PGN file");
14792 gameInfo.site = StrSave(HostName());
14793 gameInfo.date = PGNDate();
14794 gameInfo.round = StrSave("-");
14795 gameInfo.white = StrSave("?");
14796 gameInfo.black = StrSave("?");
14805 ReplaceComment(index, text)
14813 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14814 pvInfoList[index-1].depth == len &&
14815 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14816 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14817 while (*text == '\n') text++;
14818 len = strlen(text);
14819 while (len > 0 && text[len - 1] == '\n') len--;
14821 if (commentList[index] != NULL)
14822 free(commentList[index]);
14825 commentList[index] = NULL;
14828 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14829 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14830 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14831 commentList[index] = (char *) malloc(len + 2);
14832 strncpy(commentList[index], text, len);
14833 commentList[index][len] = '\n';
14834 commentList[index][len + 1] = NULLCHAR;
14836 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14838 commentList[index] = (char *) malloc(len + 7);
14839 safeStrCpy(commentList[index], "{\n", 3);
14840 safeStrCpy(commentList[index]+2, text, len+1);
14841 commentList[index][len+2] = NULLCHAR;
14842 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14843 strcat(commentList[index], "\n}\n");
14857 if (ch == '\r') continue;
14859 } while (ch != '\0');
14863 AppendComment(index, text, addBraces)
14866 Boolean addBraces; // [HGM] braces: tells if we should add {}
14871 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14872 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14875 while (*text == '\n') text++;
14876 len = strlen(text);
14877 while (len > 0 && text[len - 1] == '\n') len--;
14879 if (len == 0) return;
14881 if (commentList[index] != NULL) {
14882 Boolean addClosingBrace = addBraces;
14883 old = commentList[index];
14884 oldlen = strlen(old);
14885 while(commentList[index][oldlen-1] == '\n')
14886 commentList[index][--oldlen] = NULLCHAR;
14887 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14888 safeStrCpy(commentList[index], old, oldlen + len + 6);
14890 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14891 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14892 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14893 while (*text == '\n') { text++; len--; }
14894 commentList[index][--oldlen] = NULLCHAR;
14896 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14897 else strcat(commentList[index], "\n");
14898 strcat(commentList[index], text);
14899 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14900 else strcat(commentList[index], "\n");
14902 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14904 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14905 else commentList[index][0] = NULLCHAR;
14906 strcat(commentList[index], text);
14907 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14908 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14912 static char * FindStr( char * text, char * sub_text )
14914 char * result = strstr( text, sub_text );
14916 if( result != NULL ) {
14917 result += strlen( sub_text );
14923 /* [AS] Try to extract PV info from PGN comment */
14924 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14925 char *GetInfoFromComment( int index, char * text )
14927 char * sep = text, *p;
14929 if( text != NULL && index > 0 ) {
14932 int time = -1, sec = 0, deci;
14933 char * s_eval = FindStr( text, "[%eval " );
14934 char * s_emt = FindStr( text, "[%emt " );
14936 if( s_eval != NULL || s_emt != NULL ) {
14940 if( s_eval != NULL ) {
14941 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14945 if( delim != ']' ) {
14950 if( s_emt != NULL ) {
14955 /* We expect something like: [+|-]nnn.nn/dd */
14958 if(*text != '{') return text; // [HGM] braces: must be normal comment
14960 sep = strchr( text, '/' );
14961 if( sep == NULL || sep < (text+4) ) {
14966 if(p[1] == '(') { // comment starts with PV
14967 p = strchr(p, ')'); // locate end of PV
14968 if(p == NULL || sep < p+5) return text;
14969 // at this point we have something like "{(.*) +0.23/6 ..."
14970 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14971 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14972 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14974 time = -1; sec = -1; deci = -1;
14975 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14976 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14977 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14978 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14982 if( score_lo < 0 || score_lo >= 100 ) {
14986 if(sec >= 0) time = 600*time + 10*sec; else
14987 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14989 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14991 /* [HGM] PV time: now locate end of PV info */
14992 while( *++sep >= '0' && *sep <= '9'); // strip depth
14994 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14996 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14998 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14999 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15010 pvInfoList[index-1].depth = depth;
15011 pvInfoList[index-1].score = score;
15012 pvInfoList[index-1].time = 10*time; // centi-sec
15013 if(*sep == '}') *sep = 0; else *--sep = '{';
15014 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15020 SendToProgram(message, cps)
15022 ChessProgramState *cps;
15024 int count, outCount, error;
15027 if (cps->pr == NoProc) return;
15030 if (appData.debugMode) {
15033 fprintf(debugFP, "%ld >%-6s: %s",
15034 SubtractTimeMarks(&now, &programStartTime),
15035 cps->which, message);
15038 count = strlen(message);
15039 outCount = OutputToProcess(cps->pr, message, count, &error);
15040 if (outCount < count && !exiting
15041 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15042 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15043 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15044 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15045 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15046 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15047 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15048 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15050 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15051 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15052 gameInfo.result = res;
15054 gameInfo.resultDetails = StrSave(buf);
15056 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15057 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15062 ReceiveFromProgram(isr, closure, message, count, error)
15063 InputSourceRef isr;
15071 ChessProgramState *cps = (ChessProgramState *)closure;
15073 if (isr != cps->isr) return; /* Killed intentionally */
15076 RemoveInputSource(cps->isr);
15077 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15078 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15079 _(cps->which), cps->program);
15080 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15081 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15082 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15083 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15084 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15086 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15087 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15088 gameInfo.result = res;
15090 gameInfo.resultDetails = StrSave(buf);
15092 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15093 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15095 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15096 _(cps->which), cps->program);
15097 RemoveInputSource(cps->isr);
15099 /* [AS] Program is misbehaving badly... kill it */
15100 if( count == -2 ) {
15101 DestroyChildProcess( cps->pr, 9 );
15105 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15110 if ((end_str = strchr(message, '\r')) != NULL)
15111 *end_str = NULLCHAR;
15112 if ((end_str = strchr(message, '\n')) != NULL)
15113 *end_str = NULLCHAR;
15115 if (appData.debugMode) {
15116 TimeMark now; int print = 1;
15117 char *quote = ""; char c; int i;
15119 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15120 char start = message[0];
15121 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15122 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15123 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15124 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15125 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15126 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15127 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15128 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15129 sscanf(message, "hint: %c", &c)!=1 &&
15130 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15131 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15132 print = (appData.engineComments >= 2);
15134 message[0] = start; // restore original message
15138 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15139 SubtractTimeMarks(&now, &programStartTime), cps->which,
15145 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15146 if (appData.icsEngineAnalyze) {
15147 if (strstr(message, "whisper") != NULL ||
15148 strstr(message, "kibitz") != NULL ||
15149 strstr(message, "tellics") != NULL) return;
15152 HandleMachineMove(message, cps);
15157 SendTimeControl(cps, mps, tc, inc, sd, st)
15158 ChessProgramState *cps;
15159 int mps, inc, sd, st;
15165 if( timeControl_2 > 0 ) {
15166 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15167 tc = timeControl_2;
15170 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15171 inc /= cps->timeOdds;
15172 st /= cps->timeOdds;
15174 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15177 /* Set exact time per move, normally using st command */
15178 if (cps->stKludge) {
15179 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15181 if (seconds == 0) {
15182 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15184 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15187 snprintf(buf, MSG_SIZ, "st %d\n", st);
15190 /* Set conventional or incremental time control, using level command */
15191 if (seconds == 0) {
15192 /* Note old gnuchess bug -- minutes:seconds used to not work.
15193 Fixed in later versions, but still avoid :seconds
15194 when seconds is 0. */
15195 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15197 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15198 seconds, inc/1000.);
15201 SendToProgram(buf, cps);
15203 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15204 /* Orthogonally, limit search to given depth */
15206 if (cps->sdKludge) {
15207 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15209 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15211 SendToProgram(buf, cps);
15214 if(cps->nps >= 0) { /* [HGM] nps */
15215 if(cps->supportsNPS == FALSE)
15216 cps->nps = -1; // don't use if engine explicitly says not supported!
15218 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15219 SendToProgram(buf, cps);
15224 ChessProgramState *WhitePlayer()
15225 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15227 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15228 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15234 SendTimeRemaining(cps, machineWhite)
15235 ChessProgramState *cps;
15236 int /*boolean*/ machineWhite;
15238 char message[MSG_SIZ];
15241 /* Note: this routine must be called when the clocks are stopped
15242 or when they have *just* been set or switched; otherwise
15243 it will be off by the time since the current tick started.
15245 if (machineWhite) {
15246 time = whiteTimeRemaining / 10;
15247 otime = blackTimeRemaining / 10;
15249 time = blackTimeRemaining / 10;
15250 otime = whiteTimeRemaining / 10;
15252 /* [HGM] translate opponent's time by time-odds factor */
15253 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15254 if (appData.debugMode) {
15255 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15258 if (time <= 0) time = 1;
15259 if (otime <= 0) otime = 1;
15261 snprintf(message, MSG_SIZ, "time %ld\n", time);
15262 SendToProgram(message, cps);
15264 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15265 SendToProgram(message, cps);
15269 BoolFeature(p, name, loc, cps)
15273 ChessProgramState *cps;
15276 int len = strlen(name);
15279 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15281 sscanf(*p, "%d", &val);
15283 while (**p && **p != ' ')
15285 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15286 SendToProgram(buf, cps);
15293 IntFeature(p, name, loc, cps)
15297 ChessProgramState *cps;
15300 int len = strlen(name);
15301 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15303 sscanf(*p, "%d", loc);
15304 while (**p && **p != ' ') (*p)++;
15305 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15306 SendToProgram(buf, cps);
15313 StringFeature(p, name, loc, cps)
15317 ChessProgramState *cps;
15320 int len = strlen(name);
15321 if (strncmp((*p), name, len) == 0
15322 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15324 sscanf(*p, "%[^\"]", loc);
15325 while (**p && **p != '\"') (*p)++;
15326 if (**p == '\"') (*p)++;
15327 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15328 SendToProgram(buf, cps);
15335 ParseOption(Option *opt, ChessProgramState *cps)
15336 // [HGM] options: process the string that defines an engine option, and determine
15337 // name, type, default value, and allowed value range
15339 char *p, *q, buf[MSG_SIZ];
15340 int n, min = (-1)<<31, max = 1<<31, def;
15342 if(p = strstr(opt->name, " -spin ")) {
15343 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15344 if(max < min) max = min; // enforce consistency
15345 if(def < min) def = min;
15346 if(def > max) def = max;
15351 } else if((p = strstr(opt->name, " -slider "))) {
15352 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15353 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15354 if(max < min) max = min; // enforce consistency
15355 if(def < min) def = min;
15356 if(def > max) def = max;
15360 opt->type = Spin; // Slider;
15361 } else if((p = strstr(opt->name, " -string "))) {
15362 opt->textValue = p+9;
15363 opt->type = TextBox;
15364 } else if((p = strstr(opt->name, " -file "))) {
15365 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15366 opt->textValue = p+7;
15367 opt->type = FileName; // FileName;
15368 } else if((p = strstr(opt->name, " -path "))) {
15369 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15370 opt->textValue = p+7;
15371 opt->type = PathName; // PathName;
15372 } else if(p = strstr(opt->name, " -check ")) {
15373 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15374 opt->value = (def != 0);
15375 opt->type = CheckBox;
15376 } else if(p = strstr(opt->name, " -combo ")) {
15377 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15378 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15379 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15380 opt->value = n = 0;
15381 while(q = StrStr(q, " /// ")) {
15382 n++; *q = 0; // count choices, and null-terminate each of them
15384 if(*q == '*') { // remember default, which is marked with * prefix
15388 cps->comboList[cps->comboCnt++] = q;
15390 cps->comboList[cps->comboCnt++] = NULL;
15392 opt->type = ComboBox;
15393 } else if(p = strstr(opt->name, " -button")) {
15394 opt->type = Button;
15395 } else if(p = strstr(opt->name, " -save")) {
15396 opt->type = SaveButton;
15397 } else return FALSE;
15398 *p = 0; // terminate option name
15399 // now look if the command-line options define a setting for this engine option.
15400 if(cps->optionSettings && cps->optionSettings[0])
15401 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15402 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15403 snprintf(buf, MSG_SIZ, "option %s", p);
15404 if(p = strstr(buf, ",")) *p = 0;
15405 if(q = strchr(buf, '=')) switch(opt->type) {
15407 for(n=0; n<opt->max; n++)
15408 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15411 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15415 opt->value = atoi(q+1);
15420 SendToProgram(buf, cps);
15426 FeatureDone(cps, val)
15427 ChessProgramState* cps;
15430 DelayedEventCallback cb = GetDelayedEvent();
15431 if ((cb == InitBackEnd3 && cps == &first) ||
15432 (cb == SettingsMenuIfReady && cps == &second) ||
15433 (cb == LoadEngine) ||
15434 (cb == TwoMachinesEventIfReady)) {
15435 CancelDelayedEvent();
15436 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15438 cps->initDone = val;
15441 /* Parse feature command from engine */
15443 ParseFeatures(args, cps)
15445 ChessProgramState *cps;
15453 while (*p == ' ') p++;
15454 if (*p == NULLCHAR) return;
15456 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15457 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15458 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15459 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15460 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15461 if (BoolFeature(&p, "reuse", &val, cps)) {
15462 /* Engine can disable reuse, but can't enable it if user said no */
15463 if (!val) cps->reuse = FALSE;
15466 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15467 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15468 if (gameMode == TwoMachinesPlay) {
15469 DisplayTwoMachinesTitle();
15475 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15476 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15477 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15478 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15479 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15480 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15481 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15482 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15483 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15484 if (IntFeature(&p, "done", &val, cps)) {
15485 FeatureDone(cps, val);
15488 /* Added by Tord: */
15489 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15490 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15491 /* End of additions by Tord */
15493 /* [HGM] added features: */
15494 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15495 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15496 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15497 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15498 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15499 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15500 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15501 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15502 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15503 SendToProgram(buf, cps);
15506 if(cps->nrOptions >= MAX_OPTIONS) {
15508 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15509 DisplayError(buf, 0);
15513 /* End of additions by HGM */
15515 /* unknown feature: complain and skip */
15517 while (*q && *q != '=') q++;
15518 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15519 SendToProgram(buf, cps);
15525 while (*p && *p != '\"') p++;
15526 if (*p == '\"') p++;
15528 while (*p && *p != ' ') p++;
15536 PeriodicUpdatesEvent(newState)
15539 if (newState == appData.periodicUpdates)
15542 appData.periodicUpdates=newState;
15544 /* Display type changes, so update it now */
15545 // DisplayAnalysis();
15547 /* Get the ball rolling again... */
15549 AnalysisPeriodicEvent(1);
15550 StartAnalysisClock();
15555 PonderNextMoveEvent(newState)
15558 if (newState == appData.ponderNextMove) return;
15559 if (gameMode == EditPosition) EditPositionDone(TRUE);
15561 SendToProgram("hard\n", &first);
15562 if (gameMode == TwoMachinesPlay) {
15563 SendToProgram("hard\n", &second);
15566 SendToProgram("easy\n", &first);
15567 thinkOutput[0] = NULLCHAR;
15568 if (gameMode == TwoMachinesPlay) {
15569 SendToProgram("easy\n", &second);
15572 appData.ponderNextMove = newState;
15576 NewSettingEvent(option, feature, command, value)
15578 int option, value, *feature;
15582 if (gameMode == EditPosition) EditPositionDone(TRUE);
15583 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15584 if(feature == NULL || *feature) SendToProgram(buf, &first);
15585 if (gameMode == TwoMachinesPlay) {
15586 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15591 ShowThinkingEvent()
15592 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15594 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15595 int newState = appData.showThinking
15596 // [HGM] thinking: other features now need thinking output as well
15597 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15599 if (oldState == newState) return;
15600 oldState = newState;
15601 if (gameMode == EditPosition) EditPositionDone(TRUE);
15603 SendToProgram("post\n", &first);
15604 if (gameMode == TwoMachinesPlay) {
15605 SendToProgram("post\n", &second);
15608 SendToProgram("nopost\n", &first);
15609 thinkOutput[0] = NULLCHAR;
15610 if (gameMode == TwoMachinesPlay) {
15611 SendToProgram("nopost\n", &second);
15614 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15618 AskQuestionEvent(title, question, replyPrefix, which)
15619 char *title; char *question; char *replyPrefix; char *which;
15621 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15622 if (pr == NoProc) return;
15623 AskQuestion(title, question, replyPrefix, pr);
15627 TypeInEvent(char firstChar)
15629 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15630 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15631 gameMode == AnalyzeMode || gameMode == EditGame ||
15632 gameMode == EditPosition || gameMode == IcsExamining ||
15633 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15634 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15635 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15636 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15637 gameMode == Training) PopUpMoveDialog(firstChar);
15641 TypeInDoneEvent(char *move)
15644 int n, fromX, fromY, toX, toY;
15646 ChessMove moveType;
15649 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15650 EditPositionPasteFEN(move);
15653 // [HGM] movenum: allow move number to be typed in any mode
15654 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15659 if (gameMode != EditGame && currentMove != forwardMostMove &&
15660 gameMode != Training) {
15661 DisplayMoveError(_("Displayed move is not current"));
15663 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15664 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15665 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15666 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15667 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15668 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15670 DisplayMoveError(_("Could not parse move"));
15676 DisplayMove(moveNumber)
15679 char message[MSG_SIZ];
15681 char cpThinkOutput[MSG_SIZ];
15683 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15685 if (moveNumber == forwardMostMove - 1 ||
15686 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15688 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15690 if (strchr(cpThinkOutput, '\n')) {
15691 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15694 *cpThinkOutput = NULLCHAR;
15697 /* [AS] Hide thinking from human user */
15698 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15699 *cpThinkOutput = NULLCHAR;
15700 if( thinkOutput[0] != NULLCHAR ) {
15703 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15704 cpThinkOutput[i] = '.';
15706 cpThinkOutput[i] = NULLCHAR;
15707 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15711 if (moveNumber == forwardMostMove - 1 &&
15712 gameInfo.resultDetails != NULL) {
15713 if (gameInfo.resultDetails[0] == NULLCHAR) {
15714 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15716 snprintf(res, MSG_SIZ, " {%s} %s",
15717 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15723 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15724 DisplayMessage(res, cpThinkOutput);
15726 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15727 WhiteOnMove(moveNumber) ? " " : ".. ",
15728 parseList[moveNumber], res);
15729 DisplayMessage(message, cpThinkOutput);
15734 DisplayComment(moveNumber, text)
15738 char title[MSG_SIZ];
15740 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15741 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15743 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15744 WhiteOnMove(moveNumber) ? " " : ".. ",
15745 parseList[moveNumber]);
15747 if (text != NULL && (appData.autoDisplayComment || commentUp))
15748 CommentPopUp(title, text);
15751 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15752 * might be busy thinking or pondering. It can be omitted if your
15753 * gnuchess is configured to stop thinking immediately on any user
15754 * input. However, that gnuchess feature depends on the FIONREAD
15755 * ioctl, which does not work properly on some flavors of Unix.
15759 ChessProgramState *cps;
15762 if (!cps->useSigint) return;
15763 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15764 switch (gameMode) {
15765 case MachinePlaysWhite:
15766 case MachinePlaysBlack:
15767 case TwoMachinesPlay:
15768 case IcsPlayingWhite:
15769 case IcsPlayingBlack:
15772 /* Skip if we know it isn't thinking */
15773 if (!cps->maybeThinking) return;
15774 if (appData.debugMode)
15775 fprintf(debugFP, "Interrupting %s\n", cps->which);
15776 InterruptChildProcess(cps->pr);
15777 cps->maybeThinking = FALSE;
15782 #endif /*ATTENTION*/
15788 if (whiteTimeRemaining <= 0) {
15791 if (appData.icsActive) {
15792 if (appData.autoCallFlag &&
15793 gameMode == IcsPlayingBlack && !blackFlag) {
15794 SendToICS(ics_prefix);
15795 SendToICS("flag\n");
15799 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15801 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15802 if (appData.autoCallFlag) {
15803 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15810 if (blackTimeRemaining <= 0) {
15813 if (appData.icsActive) {
15814 if (appData.autoCallFlag &&
15815 gameMode == IcsPlayingWhite && !whiteFlag) {
15816 SendToICS(ics_prefix);
15817 SendToICS("flag\n");
15821 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15823 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15824 if (appData.autoCallFlag) {
15825 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15838 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15839 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15842 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15844 if ( !WhiteOnMove(forwardMostMove) ) {
15845 /* White made time control */
15846 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15847 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15848 /* [HGM] time odds: correct new time quota for time odds! */
15849 / WhitePlayer()->timeOdds;
15850 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15852 lastBlack -= blackTimeRemaining;
15853 /* Black made time control */
15854 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15855 / WhitePlayer()->other->timeOdds;
15856 lastWhite = whiteTimeRemaining;
15861 DisplayBothClocks()
15863 int wom = gameMode == EditPosition ?
15864 !blackPlaysFirst : WhiteOnMove(currentMove);
15865 DisplayWhiteClock(whiteTimeRemaining, wom);
15866 DisplayBlackClock(blackTimeRemaining, !wom);
15870 /* Timekeeping seems to be a portability nightmare. I think everyone
15871 has ftime(), but I'm really not sure, so I'm including some ifdefs
15872 to use other calls if you don't. Clocks will be less accurate if
15873 you have neither ftime nor gettimeofday.
15876 /* VS 2008 requires the #include outside of the function */
15877 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15878 #include <sys/timeb.h>
15881 /* Get the current time as a TimeMark */
15886 #if HAVE_GETTIMEOFDAY
15888 struct timeval timeVal;
15889 struct timezone timeZone;
15891 gettimeofday(&timeVal, &timeZone);
15892 tm->sec = (long) timeVal.tv_sec;
15893 tm->ms = (int) (timeVal.tv_usec / 1000L);
15895 #else /*!HAVE_GETTIMEOFDAY*/
15898 // include <sys/timeb.h> / moved to just above start of function
15899 struct timeb timeB;
15902 tm->sec = (long) timeB.time;
15903 tm->ms = (int) timeB.millitm;
15905 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15906 tm->sec = (long) time(NULL);
15912 /* Return the difference in milliseconds between two
15913 time marks. We assume the difference will fit in a long!
15916 SubtractTimeMarks(tm2, tm1)
15917 TimeMark *tm2, *tm1;
15919 return 1000L*(tm2->sec - tm1->sec) +
15920 (long) (tm2->ms - tm1->ms);
15925 * Code to manage the game clocks.
15927 * In tournament play, black starts the clock and then white makes a move.
15928 * We give the human user a slight advantage if he is playing white---the
15929 * clocks don't run until he makes his first move, so it takes zero time.
15930 * Also, we don't account for network lag, so we could get out of sync
15931 * with GNU Chess's clock -- but then, referees are always right.
15934 static TimeMark tickStartTM;
15935 static long intendedTickLength;
15938 NextTickLength(timeRemaining)
15939 long timeRemaining;
15941 long nominalTickLength, nextTickLength;
15943 if (timeRemaining > 0L && timeRemaining <= 10000L)
15944 nominalTickLength = 100L;
15946 nominalTickLength = 1000L;
15947 nextTickLength = timeRemaining % nominalTickLength;
15948 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15950 return nextTickLength;
15953 /* Adjust clock one minute up or down */
15955 AdjustClock(Boolean which, int dir)
15957 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15958 if(which) blackTimeRemaining += 60000*dir;
15959 else whiteTimeRemaining += 60000*dir;
15960 DisplayBothClocks();
15961 adjustedClock = TRUE;
15964 /* Stop clocks and reset to a fresh time control */
15968 (void) StopClockTimer();
15969 if (appData.icsActive) {
15970 whiteTimeRemaining = blackTimeRemaining = 0;
15971 } else if (searchTime) {
15972 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15973 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15974 } else { /* [HGM] correct new time quote for time odds */
15975 whiteTC = blackTC = fullTimeControlString;
15976 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15977 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15979 if (whiteFlag || blackFlag) {
15981 whiteFlag = blackFlag = FALSE;
15983 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15984 DisplayBothClocks();
15985 adjustedClock = FALSE;
15988 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15990 /* Decrement running clock by amount of time that has passed */
15994 long timeRemaining;
15995 long lastTickLength, fudge;
15998 if (!appData.clockMode) return;
15999 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16003 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16005 /* Fudge if we woke up a little too soon */
16006 fudge = intendedTickLength - lastTickLength;
16007 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16009 if (WhiteOnMove(forwardMostMove)) {
16010 if(whiteNPS >= 0) lastTickLength = 0;
16011 timeRemaining = whiteTimeRemaining -= lastTickLength;
16012 if(timeRemaining < 0 && !appData.icsActive) {
16013 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16014 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16015 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16016 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16019 DisplayWhiteClock(whiteTimeRemaining - fudge,
16020 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16022 if(blackNPS >= 0) lastTickLength = 0;
16023 timeRemaining = blackTimeRemaining -= lastTickLength;
16024 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16025 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16027 blackStartMove = forwardMostMove;
16028 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16031 DisplayBlackClock(blackTimeRemaining - fudge,
16032 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16034 if (CheckFlags()) return;
16037 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16038 StartClockTimer(intendedTickLength);
16040 /* if the time remaining has fallen below the alarm threshold, sound the
16041 * alarm. if the alarm has sounded and (due to a takeback or time control
16042 * with increment) the time remaining has increased to a level above the
16043 * threshold, reset the alarm so it can sound again.
16046 if (appData.icsActive && appData.icsAlarm) {
16048 /* make sure we are dealing with the user's clock */
16049 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16050 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16053 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16054 alarmSounded = FALSE;
16055 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16057 alarmSounded = TRUE;
16063 /* A player has just moved, so stop the previously running
16064 clock and (if in clock mode) start the other one.
16065 We redisplay both clocks in case we're in ICS mode, because
16066 ICS gives us an update to both clocks after every move.
16067 Note that this routine is called *after* forwardMostMove
16068 is updated, so the last fractional tick must be subtracted
16069 from the color that is *not* on move now.
16072 SwitchClocks(int newMoveNr)
16074 long lastTickLength;
16076 int flagged = FALSE;
16080 if (StopClockTimer() && appData.clockMode) {
16081 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16082 if (!WhiteOnMove(forwardMostMove)) {
16083 if(blackNPS >= 0) lastTickLength = 0;
16084 blackTimeRemaining -= lastTickLength;
16085 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16086 // if(pvInfoList[forwardMostMove].time == -1)
16087 pvInfoList[forwardMostMove].time = // use GUI time
16088 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16090 if(whiteNPS >= 0) lastTickLength = 0;
16091 whiteTimeRemaining -= lastTickLength;
16092 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16093 // if(pvInfoList[forwardMostMove].time == -1)
16094 pvInfoList[forwardMostMove].time =
16095 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16097 flagged = CheckFlags();
16099 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16100 CheckTimeControl();
16102 if (flagged || !appData.clockMode) return;
16104 switch (gameMode) {
16105 case MachinePlaysBlack:
16106 case MachinePlaysWhite:
16107 case BeginningOfGame:
16108 if (pausing) return;
16112 case PlayFromGameFile:
16120 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16121 if(WhiteOnMove(forwardMostMove))
16122 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16123 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16127 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16128 whiteTimeRemaining : blackTimeRemaining);
16129 StartClockTimer(intendedTickLength);
16133 /* Stop both clocks */
16137 long lastTickLength;
16140 if (!StopClockTimer()) return;
16141 if (!appData.clockMode) return;
16145 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16146 if (WhiteOnMove(forwardMostMove)) {
16147 if(whiteNPS >= 0) lastTickLength = 0;
16148 whiteTimeRemaining -= lastTickLength;
16149 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16151 if(blackNPS >= 0) lastTickLength = 0;
16152 blackTimeRemaining -= lastTickLength;
16153 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16158 /* Start clock of player on move. Time may have been reset, so
16159 if clock is already running, stop and restart it. */
16163 (void) StopClockTimer(); /* in case it was running already */
16164 DisplayBothClocks();
16165 if (CheckFlags()) return;
16167 if (!appData.clockMode) return;
16168 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16170 GetTimeMark(&tickStartTM);
16171 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16172 whiteTimeRemaining : blackTimeRemaining);
16174 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16175 whiteNPS = blackNPS = -1;
16176 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16177 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16178 whiteNPS = first.nps;
16179 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16180 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16181 blackNPS = first.nps;
16182 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16183 whiteNPS = second.nps;
16184 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16185 blackNPS = second.nps;
16186 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16188 StartClockTimer(intendedTickLength);
16195 long second, minute, hour, day;
16197 static char buf[32];
16199 if (ms > 0 && ms <= 9900) {
16200 /* convert milliseconds to tenths, rounding up */
16201 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16203 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16207 /* convert milliseconds to seconds, rounding up */
16208 /* use floating point to avoid strangeness of integer division
16209 with negative dividends on many machines */
16210 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16217 day = second / (60 * 60 * 24);
16218 second = second % (60 * 60 * 24);
16219 hour = second / (60 * 60);
16220 second = second % (60 * 60);
16221 minute = second / 60;
16222 second = second % 60;
16225 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16226 sign, day, hour, minute, second);
16228 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16230 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16237 * This is necessary because some C libraries aren't ANSI C compliant yet.
16240 StrStr(string, match)
16241 char *string, *match;
16245 length = strlen(match);
16247 for (i = strlen(string) - length; i >= 0; i--, string++)
16248 if (!strncmp(match, string, length))
16255 StrCaseStr(string, match)
16256 char *string, *match;
16260 length = strlen(match);
16262 for (i = strlen(string) - length; i >= 0; i--, string++) {
16263 for (j = 0; j < length; j++) {
16264 if (ToLower(match[j]) != ToLower(string[j]))
16267 if (j == length) return string;
16281 c1 = ToLower(*s1++);
16282 c2 = ToLower(*s2++);
16283 if (c1 > c2) return 1;
16284 if (c1 < c2) return -1;
16285 if (c1 == NULLCHAR) return 0;
16294 return isupper(c) ? tolower(c) : c;
16302 return islower(c) ? toupper(c) : c;
16304 #endif /* !_amigados */
16312 if ((ret = (char *) malloc(strlen(s) + 1)))
16314 safeStrCpy(ret, s, strlen(s)+1);
16320 StrSavePtr(s, savePtr)
16321 char *s, **savePtr;
16326 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16327 safeStrCpy(*savePtr, s, strlen(s)+1);
16339 clock = time((time_t *)NULL);
16340 tm = localtime(&clock);
16341 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16342 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16343 return StrSave(buf);
16348 PositionToFEN(move, overrideCastling)
16350 char *overrideCastling;
16352 int i, j, fromX, fromY, toX, toY;
16359 whiteToPlay = (gameMode == EditPosition) ?
16360 !blackPlaysFirst : (move % 2 == 0);
16363 /* Piece placement data */
16364 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16365 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16367 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16368 if (boards[move][i][j] == EmptySquare) {
16370 } else { ChessSquare piece = boards[move][i][j];
16371 if (emptycount > 0) {
16372 if(emptycount<10) /* [HGM] can be >= 10 */
16373 *p++ = '0' + emptycount;
16374 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16377 if(PieceToChar(piece) == '+') {
16378 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16380 piece = (ChessSquare)(DEMOTED piece);
16382 *p++ = PieceToChar(piece);
16384 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16385 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16390 if (emptycount > 0) {
16391 if(emptycount<10) /* [HGM] can be >= 10 */
16392 *p++ = '0' + emptycount;
16393 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16400 /* [HGM] print Crazyhouse or Shogi holdings */
16401 if( gameInfo.holdingsWidth ) {
16402 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16404 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16405 piece = boards[move][i][BOARD_WIDTH-1];
16406 if( piece != EmptySquare )
16407 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16408 *p++ = PieceToChar(piece);
16410 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16411 piece = boards[move][BOARD_HEIGHT-i-1][0];
16412 if( piece != EmptySquare )
16413 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16414 *p++ = PieceToChar(piece);
16417 if( q == p ) *p++ = '-';
16423 *p++ = whiteToPlay ? 'w' : 'b';
16426 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16427 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16429 if(nrCastlingRights) {
16431 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16432 /* [HGM] write directly from rights */
16433 if(boards[move][CASTLING][2] != NoRights &&
16434 boards[move][CASTLING][0] != NoRights )
16435 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16436 if(boards[move][CASTLING][2] != NoRights &&
16437 boards[move][CASTLING][1] != NoRights )
16438 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16439 if(boards[move][CASTLING][5] != NoRights &&
16440 boards[move][CASTLING][3] != NoRights )
16441 *p++ = boards[move][CASTLING][3] + AAA;
16442 if(boards[move][CASTLING][5] != NoRights &&
16443 boards[move][CASTLING][4] != NoRights )
16444 *p++ = boards[move][CASTLING][4] + AAA;
16447 /* [HGM] write true castling rights */
16448 if( nrCastlingRights == 6 ) {
16449 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16450 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16451 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16452 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16453 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16454 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16455 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16456 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16459 if (q == p) *p++ = '-'; /* No castling rights */
16463 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16464 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16465 /* En passant target square */
16466 if (move > backwardMostMove) {
16467 fromX = moveList[move - 1][0] - AAA;
16468 fromY = moveList[move - 1][1] - ONE;
16469 toX = moveList[move - 1][2] - AAA;
16470 toY = moveList[move - 1][3] - ONE;
16471 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16472 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16473 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16475 /* 2-square pawn move just happened */
16477 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16481 } else if(move == backwardMostMove) {
16482 // [HGM] perhaps we should always do it like this, and forget the above?
16483 if((signed char)boards[move][EP_STATUS] >= 0) {
16484 *p++ = boards[move][EP_STATUS] + AAA;
16485 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16496 /* [HGM] find reversible plies */
16497 { int i = 0, j=move;
16499 if (appData.debugMode) { int k;
16500 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16501 for(k=backwardMostMove; k<=forwardMostMove; k++)
16502 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16506 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16507 if( j == backwardMostMove ) i += initialRulePlies;
16508 sprintf(p, "%d ", i);
16509 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16511 /* Fullmove number */
16512 sprintf(p, "%d", (move / 2) + 1);
16514 return StrSave(buf);
16518 ParseFEN(board, blackPlaysFirst, fen)
16520 int *blackPlaysFirst;
16530 /* [HGM] by default clear Crazyhouse holdings, if present */
16531 if(gameInfo.holdingsWidth) {
16532 for(i=0; i<BOARD_HEIGHT; i++) {
16533 board[i][0] = EmptySquare; /* black holdings */
16534 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16535 board[i][1] = (ChessSquare) 0; /* black counts */
16536 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16540 /* Piece placement data */
16541 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16544 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16545 if (*p == '/') p++;
16546 emptycount = gameInfo.boardWidth - j;
16547 while (emptycount--)
16548 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16550 #if(BOARD_FILES >= 10)
16551 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16552 p++; emptycount=10;
16553 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16554 while (emptycount--)
16555 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16557 } else if (isdigit(*p)) {
16558 emptycount = *p++ - '0';
16559 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16560 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16561 while (emptycount--)
16562 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16563 } else if (*p == '+' || isalpha(*p)) {
16564 if (j >= gameInfo.boardWidth) return FALSE;
16566 piece = CharToPiece(*++p);
16567 if(piece == EmptySquare) return FALSE; /* unknown piece */
16568 piece = (ChessSquare) (PROMOTED piece ); p++;
16569 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16570 } else piece = CharToPiece(*p++);
16572 if(piece==EmptySquare) return FALSE; /* unknown piece */
16573 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16574 piece = (ChessSquare) (PROMOTED piece);
16575 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16578 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16584 while (*p == '/' || *p == ' ') p++;
16586 /* [HGM] look for Crazyhouse holdings here */
16587 while(*p==' ') p++;
16588 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16590 if(*p == '-' ) p++; /* empty holdings */ else {
16591 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16592 /* if we would allow FEN reading to set board size, we would */
16593 /* have to add holdings and shift the board read so far here */
16594 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16596 if((int) piece >= (int) BlackPawn ) {
16597 i = (int)piece - (int)BlackPawn;
16598 i = PieceToNumber((ChessSquare)i);
16599 if( i >= gameInfo.holdingsSize ) return FALSE;
16600 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16601 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16603 i = (int)piece - (int)WhitePawn;
16604 i = PieceToNumber((ChessSquare)i);
16605 if( i >= gameInfo.holdingsSize ) return FALSE;
16606 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16607 board[i][BOARD_WIDTH-2]++; /* black holdings */
16614 while(*p == ' ') p++;
16618 if(appData.colorNickNames) {
16619 if( c == appData.colorNickNames[0] ) c = 'w'; else
16620 if( c == appData.colorNickNames[1] ) c = 'b';
16624 *blackPlaysFirst = FALSE;
16627 *blackPlaysFirst = TRUE;
16633 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16634 /* return the extra info in global variiables */
16636 /* set defaults in case FEN is incomplete */
16637 board[EP_STATUS] = EP_UNKNOWN;
16638 for(i=0; i<nrCastlingRights; i++ ) {
16639 board[CASTLING][i] =
16640 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16641 } /* assume possible unless obviously impossible */
16642 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16643 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16644 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16645 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16646 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16647 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16648 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16649 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16652 while(*p==' ') p++;
16653 if(nrCastlingRights) {
16654 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16655 /* castling indicator present, so default becomes no castlings */
16656 for(i=0; i<nrCastlingRights; i++ ) {
16657 board[CASTLING][i] = NoRights;
16660 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16661 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16662 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16663 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16664 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16666 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16667 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16668 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16670 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16671 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16672 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16673 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16674 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16675 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16678 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16679 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16680 board[CASTLING][2] = whiteKingFile;
16683 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16684 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16685 board[CASTLING][2] = whiteKingFile;
16688 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16689 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16690 board[CASTLING][5] = blackKingFile;
16693 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16694 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16695 board[CASTLING][5] = blackKingFile;
16698 default: /* FRC castlings */
16699 if(c >= 'a') { /* black rights */
16700 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16701 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16702 if(i == BOARD_RGHT) break;
16703 board[CASTLING][5] = i;
16705 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16706 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16708 board[CASTLING][3] = c;
16710 board[CASTLING][4] = c;
16711 } else { /* white rights */
16712 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16713 if(board[0][i] == WhiteKing) break;
16714 if(i == BOARD_RGHT) break;
16715 board[CASTLING][2] = i;
16716 c -= AAA - 'a' + 'A';
16717 if(board[0][c] >= WhiteKing) break;
16719 board[CASTLING][0] = c;
16721 board[CASTLING][1] = c;
16725 for(i=0; i<nrCastlingRights; i++)
16726 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16727 if (appData.debugMode) {
16728 fprintf(debugFP, "FEN castling rights:");
16729 for(i=0; i<nrCastlingRights; i++)
16730 fprintf(debugFP, " %d", board[CASTLING][i]);
16731 fprintf(debugFP, "\n");
16734 while(*p==' ') p++;
16737 /* read e.p. field in games that know e.p. capture */
16738 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16739 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16741 p++; board[EP_STATUS] = EP_NONE;
16743 char c = *p++ - AAA;
16745 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16746 if(*p >= '0' && *p <='9') p++;
16747 board[EP_STATUS] = c;
16752 if(sscanf(p, "%d", &i) == 1) {
16753 FENrulePlies = i; /* 50-move ply counter */
16754 /* (The move number is still ignored) */
16761 EditPositionPasteFEN(char *fen)
16764 Board initial_position;
16766 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16767 DisplayError(_("Bad FEN position in clipboard"), 0);
16770 int savedBlackPlaysFirst = blackPlaysFirst;
16771 EditPositionEvent();
16772 blackPlaysFirst = savedBlackPlaysFirst;
16773 CopyBoard(boards[0], initial_position);
16774 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16775 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16776 DisplayBothClocks();
16777 DrawPosition(FALSE, boards[currentMove]);
16782 static char cseq[12] = "\\ ";
16784 Boolean set_cont_sequence(char *new_seq)
16789 // handle bad attempts to set the sequence
16791 return 0; // acceptable error - no debug
16793 len = strlen(new_seq);
16794 ret = (len > 0) && (len < sizeof(cseq));
16796 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16797 else if (appData.debugMode)
16798 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16803 reformat a source message so words don't cross the width boundary. internal
16804 newlines are not removed. returns the wrapped size (no null character unless
16805 included in source message). If dest is NULL, only calculate the size required
16806 for the dest buffer. lp argument indicats line position upon entry, and it's
16807 passed back upon exit.
16809 int wrap(char *dest, char *src, int count, int width, int *lp)
16811 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16813 cseq_len = strlen(cseq);
16814 old_line = line = *lp;
16815 ansi = len = clen = 0;
16817 for (i=0; i < count; i++)
16819 if (src[i] == '\033')
16822 // if we hit the width, back up
16823 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16825 // store i & len in case the word is too long
16826 old_i = i, old_len = len;
16828 // find the end of the last word
16829 while (i && src[i] != ' ' && src[i] != '\n')
16835 // word too long? restore i & len before splitting it
16836 if ((old_i-i+clen) >= width)
16843 if (i && src[i-1] == ' ')
16846 if (src[i] != ' ' && src[i] != '\n')
16853 // now append the newline and continuation sequence
16858 strncpy(dest+len, cseq, cseq_len);
16866 dest[len] = src[i];
16870 if (src[i] == '\n')
16875 if (dest && appData.debugMode)
16877 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16878 count, width, line, len, *lp);
16879 show_bytes(debugFP, src, count);
16880 fprintf(debugFP, "\ndest: ");
16881 show_bytes(debugFP, dest, len);
16882 fprintf(debugFP, "\n");
16884 *lp = dest ? line : old_line;
16889 // [HGM] vari: routines for shelving variations
16890 Boolean modeRestore = FALSE;
16893 PushInner(int firstMove, int lastMove)
16895 int i, j, nrMoves = lastMove - firstMove;
16897 // push current tail of game on stack
16898 savedResult[storedGames] = gameInfo.result;
16899 savedDetails[storedGames] = gameInfo.resultDetails;
16900 gameInfo.resultDetails = NULL;
16901 savedFirst[storedGames] = firstMove;
16902 savedLast [storedGames] = lastMove;
16903 savedFramePtr[storedGames] = framePtr;
16904 framePtr -= nrMoves; // reserve space for the boards
16905 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16906 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16907 for(j=0; j<MOVE_LEN; j++)
16908 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16909 for(j=0; j<2*MOVE_LEN; j++)
16910 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16911 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16912 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16913 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16914 pvInfoList[firstMove+i-1].depth = 0;
16915 commentList[framePtr+i] = commentList[firstMove+i];
16916 commentList[firstMove+i] = NULL;
16920 forwardMostMove = firstMove; // truncate game so we can start variation
16924 PushTail(int firstMove, int lastMove)
16926 if(appData.icsActive) { // only in local mode
16927 forwardMostMove = currentMove; // mimic old ICS behavior
16930 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16932 PushInner(firstMove, lastMove);
16933 if(storedGames == 1) GreyRevert(FALSE);
16934 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16938 PopInner(Boolean annotate)
16941 char buf[8000], moveBuf[20];
16943 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16944 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16945 nrMoves = savedLast[storedGames] - currentMove;
16948 if(!WhiteOnMove(currentMove))
16949 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16950 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16951 for(i=currentMove; i<forwardMostMove; i++) {
16953 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16954 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16955 strcat(buf, moveBuf);
16956 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16957 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16961 for(i=1; i<=nrMoves; i++) { // copy last variation back
16962 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16963 for(j=0; j<MOVE_LEN; j++)
16964 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16965 for(j=0; j<2*MOVE_LEN; j++)
16966 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16967 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16968 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16969 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16970 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16971 commentList[currentMove+i] = commentList[framePtr+i];
16972 commentList[framePtr+i] = NULL;
16974 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16975 framePtr = savedFramePtr[storedGames];
16976 gameInfo.result = savedResult[storedGames];
16977 if(gameInfo.resultDetails != NULL) {
16978 free(gameInfo.resultDetails);
16980 gameInfo.resultDetails = savedDetails[storedGames];
16981 forwardMostMove = currentMove + nrMoves;
16985 PopTail(Boolean annotate)
16987 if(appData.icsActive) return FALSE; // only in local mode
16988 if(!storedGames) return FALSE; // sanity
16989 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16991 PopInner(annotate);
16992 if(currentMove < forwardMostMove) ForwardEvent(); else
16993 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16995 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17001 { // remove all shelved variations
17003 for(i=0; i<storedGames; i++) {
17004 if(savedDetails[i])
17005 free(savedDetails[i]);
17006 savedDetails[i] = NULL;
17008 for(i=framePtr; i<MAX_MOVES; i++) {
17009 if(commentList[i]) free(commentList[i]);
17010 commentList[i] = NULL;
17012 framePtr = MAX_MOVES-1;
17017 LoadVariation(int index, char *text)
17018 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17019 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17020 int level = 0, move;
17022 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17023 // first find outermost bracketing variation
17024 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17025 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17026 if(*p == '{') wait = '}'; else
17027 if(*p == '[') wait = ']'; else
17028 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17029 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17031 if(*p == wait) wait = NULLCHAR; // closing ]} found
17034 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17035 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17036 end[1] = NULLCHAR; // clip off comment beyond variation
17037 ToNrEvent(currentMove-1);
17038 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17039 // kludge: use ParsePV() to append variation to game
17040 move = currentMove;
17041 ParsePV(start, TRUE, TRUE);
17042 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17043 ClearPremoveHighlights();
17045 ToNrEvent(currentMove+1);