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 Boolean epOK, flipSearch;
11188 unsigned char piece, to;
11191 #define DATABASESIZE 10000000 /* good for 100k games */
11192 Move moveDatabase[DATABASESIZE];
11195 void MakePieceList(Board board, int *counts)
11197 int r, f, n=Q_PROMO;
11198 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11199 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11200 int sq = f + (r<<4);
11201 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11202 quickBoard[sq] = ++n;
11204 pieceType[n] = board[r][f];
11205 counts[board[r][f]]++;
11206 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11207 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11210 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11213 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11215 int sq = fromX + (fromY<<4);
11216 int piece = quickBoard[sq];
11217 quickBoard[sq] = 0;
11218 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11219 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11220 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11221 moveDatabase[movePtr++].piece = Q_WCASTL;
11222 quickBoard[sq] = piece;
11223 piece = quickBoard[from]; quickBoard[from] = 0;
11224 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11226 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11227 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11228 moveDatabase[movePtr++].piece = Q_BCASTL;
11229 quickBoard[sq] = piece;
11230 piece = quickBoard[from]; quickBoard[from] = 0;
11231 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11233 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11234 quickBoard[(fromY<<4)+toX] = 0;
11235 moveDatabase[movePtr].piece = Q_EP;
11236 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11237 moveDatabase[movePtr].to = sq;
11239 if(promoPiece != pieceType[piece]) {
11240 moveDatabase[movePtr++].piece = Q_PROMO;
11241 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11243 moveDatabase[movePtr].piece = piece;
11244 quickBoard[sq] = piece;
11248 int PackGame(Board board)
11250 moveDatabase[movePtr].piece = 0; // terminate previous game
11251 if(movePtr > DATABASESIZE - 500) return 0; // gamble on that game will not be more than 250 moves
11253 MakePieceList(board, counts);
11257 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11258 { // compare according to search mode
11260 switch(appData.searchMode)
11262 case 1: // exact position match
11263 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11264 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11267 case 2: // can have extra material on empty squares
11268 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11269 if(board[r][f] == EmptySquare) continue;
11270 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11273 case 3: // material with exact Pawn structure
11274 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11275 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11276 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11277 } // fall through to material comparison
11278 case 4: // exact material
11279 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11281 case 6: // material range with given imbalance
11282 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11283 // fall through to range comparison
11284 case 5: // material range
11285 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11290 int QuickScan(Board board, Move *move)
11291 { // reconstruct game,and compare all positions in it
11292 int cnt=0, stretch=0;
11293 MakePieceList(board, counts);
11295 int piece = move->piece;
11296 int to = move->to, from = pieceList[piece];
11297 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11298 if(!piece) return -1;
11299 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11300 piece = (++move)->piece;
11301 from = pieceList[piece];
11302 counts[pieceType[piece]]--;
11303 pieceType[piece] = (ChessSquare) move->to;
11304 counts[move->to]++;
11305 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11306 counts[pieceType[quickBoard[to]]]--;
11307 quickBoard[to] = 0;
11310 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11311 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11312 from = pieceList[piece]; // so this must be King
11313 quickBoard[from] = 0;
11314 quickBoard[to] = piece;
11315 pieceList[piece] = to;
11320 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11321 quickBoard[from] = 0;
11322 quickBoard[to] = piece;
11323 pieceList[piece] = to;
11325 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11326 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11327 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11328 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11330 static int lastCounts[EmptySquare+1];
11332 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11333 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11334 } else stretch = 0;
11335 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11343 flipSearch = FALSE;
11344 CopyBoard(soughtBoard, boards[currentMove]);
11345 MakePieceList(soughtBoard, maxSought);
11346 CopyBoard(reverseBoard, boards[currentMove]);
11347 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11348 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11349 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11350 reverseBoard[r][f] = piece;
11352 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11353 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11354 || (boards[currentMove][CASTLING][2] == NoRights ||
11355 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11356 && (boards[currentMove][CASTLING][5] == NoRights ||
11357 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11360 CopyBoard(flipBoard, soughtBoard);
11361 CopyBoard(rotateBoard, reverseBoard);
11362 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11363 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11364 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11367 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11368 if(appData.searchMode >= 5) {
11369 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11370 MakePieceList(soughtBoard, minSought);
11371 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11375 GameInfo dummyInfo;
11377 int GameContainsPosition(FILE *f, ListGame *lg)
11379 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11380 int fromX, fromY, toX, toY;
11382 static int initDone=FALSE;
11384 // weed out games based on numerical tag comparison
11385 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11386 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11387 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11388 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11390 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11393 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11394 else CopyBoard(boards[scratch], initialPosition); // default start position
11396 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11397 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11400 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11401 fseek(f, lg->offset, 0);
11404 yyboardindex = scratch;
11405 quickFlag = plyNr+1;
11410 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11416 if(plyNr) return -1; // after we have seen moves, this is for new game
11419 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11420 case ImpossibleMove:
11421 case WhiteWins: // game ends here with these four
11424 case GameUnfinished:
11428 if(appData.testLegality) return -1;
11429 case WhiteCapturesEnPassant:
11430 case BlackCapturesEnPassant:
11431 case WhitePromotion:
11432 case BlackPromotion:
11433 case WhiteNonPromotion:
11434 case BlackNonPromotion:
11436 case WhiteKingSideCastle:
11437 case WhiteQueenSideCastle:
11438 case BlackKingSideCastle:
11439 case BlackQueenSideCastle:
11440 case WhiteKingSideCastleWild:
11441 case WhiteQueenSideCastleWild:
11442 case BlackKingSideCastleWild:
11443 case BlackQueenSideCastleWild:
11444 case WhiteHSideCastleFR:
11445 case WhiteASideCastleFR:
11446 case BlackHSideCastleFR:
11447 case BlackASideCastleFR:
11448 fromX = currentMoveString[0] - AAA;
11449 fromY = currentMoveString[1] - ONE;
11450 toX = currentMoveString[2] - AAA;
11451 toY = currentMoveString[3] - ONE;
11452 promoChar = currentMoveString[4];
11456 fromX = next == WhiteDrop ?
11457 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11458 (int) CharToPiece(ToLower(currentMoveString[0]));
11460 toX = currentMoveString[2] - AAA;
11461 toY = currentMoveString[3] - ONE;
11464 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11466 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11467 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11468 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11469 if(appData.findMirror) {
11470 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11471 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11476 /* Load the nth game from open file f */
11478 LoadGame(f, gameNumber, title, useList)
11486 int gn = gameNumber;
11487 ListGame *lg = NULL;
11488 int numPGNTags = 0;
11490 GameMode oldGameMode;
11491 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11493 if (appData.debugMode)
11494 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11496 if (gameMode == Training )
11497 SetTrainingModeOff();
11499 oldGameMode = gameMode;
11500 if (gameMode != BeginningOfGame) {
11501 Reset(FALSE, TRUE);
11505 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11506 fclose(lastLoadGameFP);
11510 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11513 fseek(f, lg->offset, 0);
11514 GameListHighlight(gameNumber);
11515 pos = lg->position;
11519 DisplayError(_("Game number out of range"), 0);
11524 if (fseek(f, 0, 0) == -1) {
11525 if (f == lastLoadGameFP ?
11526 gameNumber == lastLoadGameNumber + 1 :
11530 DisplayError(_("Can't seek on game file"), 0);
11535 lastLoadGameFP = f;
11536 lastLoadGameNumber = gameNumber;
11537 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11538 lastLoadGameUseList = useList;
11542 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11543 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11544 lg->gameInfo.black);
11546 } else if (*title != NULLCHAR) {
11547 if (gameNumber > 1) {
11548 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11551 DisplayTitle(title);
11555 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11556 gameMode = PlayFromGameFile;
11560 currentMove = forwardMostMove = backwardMostMove = 0;
11561 CopyBoard(boards[0], initialPosition);
11565 * Skip the first gn-1 games in the file.
11566 * Also skip over anything that precedes an identifiable
11567 * start of game marker, to avoid being confused by
11568 * garbage at the start of the file. Currently
11569 * recognized start of game markers are the move number "1",
11570 * the pattern "gnuchess .* game", the pattern
11571 * "^[#;%] [^ ]* game file", and a PGN tag block.
11572 * A game that starts with one of the latter two patterns
11573 * will also have a move number 1, possibly
11574 * following a position diagram.
11575 * 5-4-02: Let's try being more lenient and allowing a game to
11576 * start with an unnumbered move. Does that break anything?
11578 cm = lastLoadGameStart = EndOfFile;
11580 yyboardindex = forwardMostMove;
11581 cm = (ChessMove) Myylex();
11584 if (cmailMsgLoaded) {
11585 nCmailGames = CMAIL_MAX_GAMES - gn;
11588 DisplayError(_("Game not found in file"), 0);
11595 lastLoadGameStart = cm;
11598 case MoveNumberOne:
11599 switch (lastLoadGameStart) {
11604 case MoveNumberOne:
11606 gn--; /* count this game */
11607 lastLoadGameStart = cm;
11616 switch (lastLoadGameStart) {
11619 case MoveNumberOne:
11621 gn--; /* count this game */
11622 lastLoadGameStart = cm;
11625 lastLoadGameStart = cm; /* game counted already */
11633 yyboardindex = forwardMostMove;
11634 cm = (ChessMove) Myylex();
11635 } while (cm == PGNTag || cm == Comment);
11642 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11643 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11644 != CMAIL_OLD_RESULT) {
11646 cmailResult[ CMAIL_MAX_GAMES
11647 - gn - 1] = CMAIL_OLD_RESULT;
11653 /* Only a NormalMove can be at the start of a game
11654 * without a position diagram. */
11655 if (lastLoadGameStart == EndOfFile ) {
11657 lastLoadGameStart = MoveNumberOne;
11666 if (appData.debugMode)
11667 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11669 if (cm == XBoardGame) {
11670 /* Skip any header junk before position diagram and/or move 1 */
11672 yyboardindex = forwardMostMove;
11673 cm = (ChessMove) Myylex();
11675 if (cm == EndOfFile ||
11676 cm == GNUChessGame || cm == XBoardGame) {
11677 /* Empty game; pretend end-of-file and handle later */
11682 if (cm == MoveNumberOne || cm == PositionDiagram ||
11683 cm == PGNTag || cm == Comment)
11686 } else if (cm == GNUChessGame) {
11687 if (gameInfo.event != NULL) {
11688 free(gameInfo.event);
11690 gameInfo.event = StrSave(yy_text);
11693 startedFromSetupPosition = FALSE;
11694 while (cm == PGNTag) {
11695 if (appData.debugMode)
11696 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11697 err = ParsePGNTag(yy_text, &gameInfo);
11698 if (!err) numPGNTags++;
11700 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11701 if(gameInfo.variant != oldVariant) {
11702 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11703 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11704 InitPosition(TRUE);
11705 oldVariant = gameInfo.variant;
11706 if (appData.debugMode)
11707 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11711 if (gameInfo.fen != NULL) {
11712 Board initial_position;
11713 startedFromSetupPosition = TRUE;
11714 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11716 DisplayError(_("Bad FEN position in file"), 0);
11719 CopyBoard(boards[0], initial_position);
11720 if (blackPlaysFirst) {
11721 currentMove = forwardMostMove = backwardMostMove = 1;
11722 CopyBoard(boards[1], initial_position);
11723 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11724 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11725 timeRemaining[0][1] = whiteTimeRemaining;
11726 timeRemaining[1][1] = blackTimeRemaining;
11727 if (commentList[0] != NULL) {
11728 commentList[1] = commentList[0];
11729 commentList[0] = NULL;
11732 currentMove = forwardMostMove = backwardMostMove = 0;
11734 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11736 initialRulePlies = FENrulePlies;
11737 for( i=0; i< nrCastlingRights; i++ )
11738 initialRights[i] = initial_position[CASTLING][i];
11740 yyboardindex = forwardMostMove;
11741 free(gameInfo.fen);
11742 gameInfo.fen = NULL;
11745 yyboardindex = forwardMostMove;
11746 cm = (ChessMove) Myylex();
11748 /* Handle comments interspersed among the tags */
11749 while (cm == Comment) {
11751 if (appData.debugMode)
11752 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11754 AppendComment(currentMove, p, FALSE);
11755 yyboardindex = forwardMostMove;
11756 cm = (ChessMove) Myylex();
11760 /* don't rely on existence of Event tag since if game was
11761 * pasted from clipboard the Event tag may not exist
11763 if (numPGNTags > 0){
11765 if (gameInfo.variant == VariantNormal) {
11766 VariantClass v = StringToVariant(gameInfo.event);
11767 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11768 if(v < VariantShogi) gameInfo.variant = v;
11771 if( appData.autoDisplayTags ) {
11772 tags = PGNTags(&gameInfo);
11773 TagsPopUp(tags, CmailMsg());
11778 /* Make something up, but don't display it now */
11783 if (cm == PositionDiagram) {
11786 Board initial_position;
11788 if (appData.debugMode)
11789 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11791 if (!startedFromSetupPosition) {
11793 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11794 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11805 initial_position[i][j++] = CharToPiece(*p);
11808 while (*p == ' ' || *p == '\t' ||
11809 *p == '\n' || *p == '\r') p++;
11811 if (strncmp(p, "black", strlen("black"))==0)
11812 blackPlaysFirst = TRUE;
11814 blackPlaysFirst = FALSE;
11815 startedFromSetupPosition = TRUE;
11817 CopyBoard(boards[0], initial_position);
11818 if (blackPlaysFirst) {
11819 currentMove = forwardMostMove = backwardMostMove = 1;
11820 CopyBoard(boards[1], initial_position);
11821 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11822 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11823 timeRemaining[0][1] = whiteTimeRemaining;
11824 timeRemaining[1][1] = blackTimeRemaining;
11825 if (commentList[0] != NULL) {
11826 commentList[1] = commentList[0];
11827 commentList[0] = NULL;
11830 currentMove = forwardMostMove = backwardMostMove = 0;
11833 yyboardindex = forwardMostMove;
11834 cm = (ChessMove) Myylex();
11837 if (first.pr == NoProc) {
11838 StartChessProgram(&first);
11840 InitChessProgram(&first, FALSE);
11841 SendToProgram("force\n", &first);
11842 if (startedFromSetupPosition) {
11843 SendBoard(&first, forwardMostMove);
11844 if (appData.debugMode) {
11845 fprintf(debugFP, "Load Game\n");
11847 DisplayBothClocks();
11850 /* [HGM] server: flag to write setup moves in broadcast file as one */
11851 loadFlag = appData.suppressLoadMoves;
11853 while (cm == Comment) {
11855 if (appData.debugMode)
11856 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11858 AppendComment(currentMove, p, FALSE);
11859 yyboardindex = forwardMostMove;
11860 cm = (ChessMove) Myylex();
11863 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11864 cm == WhiteWins || cm == BlackWins ||
11865 cm == GameIsDrawn || cm == GameUnfinished) {
11866 DisplayMessage("", _("No moves in game"));
11867 if (cmailMsgLoaded) {
11868 if (appData.debugMode)
11869 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11873 DrawPosition(FALSE, boards[currentMove]);
11874 DisplayBothClocks();
11875 gameMode = EditGame;
11882 // [HGM] PV info: routine tests if comment empty
11883 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11884 DisplayComment(currentMove - 1, commentList[currentMove]);
11886 if (!matchMode && appData.timeDelay != 0)
11887 DrawPosition(FALSE, boards[currentMove]);
11889 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11890 programStats.ok_to_send = 1;
11893 /* if the first token after the PGN tags is a move
11894 * and not move number 1, retrieve it from the parser
11896 if (cm != MoveNumberOne)
11897 LoadGameOneMove(cm);
11899 /* load the remaining moves from the file */
11900 while (LoadGameOneMove(EndOfFile)) {
11901 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11902 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11905 /* rewind to the start of the game */
11906 currentMove = backwardMostMove;
11908 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11910 if (oldGameMode == AnalyzeFile ||
11911 oldGameMode == AnalyzeMode) {
11912 AnalyzeFileEvent();
11915 if (!matchMode && pos >= 0) {
11916 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11918 if (matchMode || appData.timeDelay == 0) {
11920 } else if (appData.timeDelay > 0) {
11921 AutoPlayGameLoop();
11924 if (appData.debugMode)
11925 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11927 loadFlag = 0; /* [HGM] true game starts */
11931 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11933 ReloadPosition(offset)
11936 int positionNumber = lastLoadPositionNumber + offset;
11937 if (lastLoadPositionFP == NULL) {
11938 DisplayError(_("No position has been loaded yet"), 0);
11941 if (positionNumber <= 0) {
11942 DisplayError(_("Can't back up any further"), 0);
11945 return LoadPosition(lastLoadPositionFP, positionNumber,
11946 lastLoadPositionTitle);
11949 /* Load the nth position from the given file */
11951 LoadPositionFromFile(filename, n, title)
11959 if (strcmp(filename, "-") == 0) {
11960 return LoadPosition(stdin, n, "stdin");
11962 f = fopen(filename, "rb");
11964 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11965 DisplayError(buf, errno);
11968 return LoadPosition(f, n, title);
11973 /* Load the nth position from the given open file, and close it */
11975 LoadPosition(f, positionNumber, title)
11977 int positionNumber;
11980 char *p, line[MSG_SIZ];
11981 Board initial_position;
11982 int i, j, fenMode, pn;
11984 if (gameMode == Training )
11985 SetTrainingModeOff();
11987 if (gameMode != BeginningOfGame) {
11988 Reset(FALSE, TRUE);
11990 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11991 fclose(lastLoadPositionFP);
11993 if (positionNumber == 0) positionNumber = 1;
11994 lastLoadPositionFP = f;
11995 lastLoadPositionNumber = positionNumber;
11996 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11997 if (first.pr == NoProc && !appData.noChessProgram) {
11998 StartChessProgram(&first);
11999 InitChessProgram(&first, FALSE);
12001 pn = positionNumber;
12002 if (positionNumber < 0) {
12003 /* Negative position number means to seek to that byte offset */
12004 if (fseek(f, -positionNumber, 0) == -1) {
12005 DisplayError(_("Can't seek on position file"), 0);
12010 if (fseek(f, 0, 0) == -1) {
12011 if (f == lastLoadPositionFP ?
12012 positionNumber == lastLoadPositionNumber + 1 :
12013 positionNumber == 1) {
12016 DisplayError(_("Can't seek on position file"), 0);
12021 /* See if this file is FEN or old-style xboard */
12022 if (fgets(line, MSG_SIZ, f) == NULL) {
12023 DisplayError(_("Position not found in file"), 0);
12026 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12027 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12030 if (fenMode || line[0] == '#') pn--;
12032 /* skip positions before number pn */
12033 if (fgets(line, MSG_SIZ, f) == NULL) {
12035 DisplayError(_("Position not found in file"), 0);
12038 if (fenMode || line[0] == '#') pn--;
12043 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12044 DisplayError(_("Bad FEN position in file"), 0);
12048 (void) fgets(line, MSG_SIZ, f);
12049 (void) fgets(line, MSG_SIZ, f);
12051 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12052 (void) fgets(line, MSG_SIZ, f);
12053 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12056 initial_position[i][j++] = CharToPiece(*p);
12060 blackPlaysFirst = FALSE;
12062 (void) fgets(line, MSG_SIZ, f);
12063 if (strncmp(line, "black", strlen("black"))==0)
12064 blackPlaysFirst = TRUE;
12067 startedFromSetupPosition = TRUE;
12069 CopyBoard(boards[0], initial_position);
12070 if (blackPlaysFirst) {
12071 currentMove = forwardMostMove = backwardMostMove = 1;
12072 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12073 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12074 CopyBoard(boards[1], initial_position);
12075 DisplayMessage("", _("Black to play"));
12077 currentMove = forwardMostMove = backwardMostMove = 0;
12078 DisplayMessage("", _("White to play"));
12080 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12081 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12082 SendToProgram("force\n", &first);
12083 SendBoard(&first, forwardMostMove);
12085 if (appData.debugMode) {
12087 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12088 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12089 fprintf(debugFP, "Load Position\n");
12092 if (positionNumber > 1) {
12093 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12094 DisplayTitle(line);
12096 DisplayTitle(title);
12098 gameMode = EditGame;
12101 timeRemaining[0][1] = whiteTimeRemaining;
12102 timeRemaining[1][1] = blackTimeRemaining;
12103 DrawPosition(FALSE, boards[currentMove]);
12110 CopyPlayerNameIntoFileName(dest, src)
12113 while (*src != NULLCHAR && *src != ',') {
12118 *(*dest)++ = *src++;
12123 char *DefaultFileName(ext)
12126 static char def[MSG_SIZ];
12129 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12131 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12133 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12135 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12142 /* Save the current game to the given file */
12144 SaveGameToFile(filename, append)
12150 int result, i, t,tot=0;
12152 if (strcmp(filename, "-") == 0) {
12153 return SaveGame(stdout, 0, NULL);
12155 for(i=0; i<10; i++) { // upto 10 tries
12156 f = fopen(filename, append ? "a" : "w");
12157 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12158 if(f || errno != 13) break;
12159 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12163 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12164 DisplayError(buf, errno);
12167 safeStrCpy(buf, lastMsg, MSG_SIZ);
12168 DisplayMessage(_("Waiting for access to save file"), "");
12169 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12170 DisplayMessage(_("Saving game"), "");
12171 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
12172 result = SaveGame(f, 0, NULL);
12173 DisplayMessage(buf, "");
12183 static char buf[MSG_SIZ];
12186 p = strchr(str, ' ');
12187 if (p == NULL) return str;
12188 strncpy(buf, str, p - str);
12189 buf[p - str] = NULLCHAR;
12193 #define PGN_MAX_LINE 75
12195 #define PGN_SIDE_WHITE 0
12196 #define PGN_SIDE_BLACK 1
12199 static int FindFirstMoveOutOfBook( int side )
12203 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12204 int index = backwardMostMove;
12205 int has_book_hit = 0;
12207 if( (index % 2) != side ) {
12211 while( index < forwardMostMove ) {
12212 /* Check to see if engine is in book */
12213 int depth = pvInfoList[index].depth;
12214 int score = pvInfoList[index].score;
12220 else if( score == 0 && depth == 63 ) {
12221 in_book = 1; /* Zappa */
12223 else if( score == 2 && depth == 99 ) {
12224 in_book = 1; /* Abrok */
12227 has_book_hit += in_book;
12243 void GetOutOfBookInfo( char * buf )
12247 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12249 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12250 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12254 if( oob[0] >= 0 || oob[1] >= 0 ) {
12255 for( i=0; i<2; i++ ) {
12259 if( i > 0 && oob[0] >= 0 ) {
12260 strcat( buf, " " );
12263 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12264 sprintf( buf+strlen(buf), "%s%.2f",
12265 pvInfoList[idx].score >= 0 ? "+" : "",
12266 pvInfoList[idx].score / 100.0 );
12272 /* Save game in PGN style and close the file */
12277 int i, offset, linelen, newblock;
12281 int movelen, numlen, blank;
12282 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12284 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12286 tm = time((time_t *) NULL);
12288 PrintPGNTags(f, &gameInfo);
12290 if (backwardMostMove > 0 || startedFromSetupPosition) {
12291 char *fen = PositionToFEN(backwardMostMove, NULL);
12292 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12293 fprintf(f, "\n{--------------\n");
12294 PrintPosition(f, backwardMostMove);
12295 fprintf(f, "--------------}\n");
12299 /* [AS] Out of book annotation */
12300 if( appData.saveOutOfBookInfo ) {
12303 GetOutOfBookInfo( buf );
12305 if( buf[0] != '\0' ) {
12306 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12313 i = backwardMostMove;
12317 while (i < forwardMostMove) {
12318 /* Print comments preceding this move */
12319 if (commentList[i] != NULL) {
12320 if (linelen > 0) fprintf(f, "\n");
12321 fprintf(f, "%s", commentList[i]);
12326 /* Format move number */
12328 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12331 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12333 numtext[0] = NULLCHAR;
12335 numlen = strlen(numtext);
12338 /* Print move number */
12339 blank = linelen > 0 && numlen > 0;
12340 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12349 fprintf(f, "%s", numtext);
12353 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12354 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12357 blank = linelen > 0 && movelen > 0;
12358 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12367 fprintf(f, "%s", move_buffer);
12368 linelen += movelen;
12370 /* [AS] Add PV info if present */
12371 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12372 /* [HGM] add time */
12373 char buf[MSG_SIZ]; int seconds;
12375 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12381 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12384 seconds = (seconds + 4)/10; // round to full seconds
12386 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12388 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12391 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12392 pvInfoList[i].score >= 0 ? "+" : "",
12393 pvInfoList[i].score / 100.0,
12394 pvInfoList[i].depth,
12397 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12399 /* Print score/depth */
12400 blank = linelen > 0 && movelen > 0;
12401 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12410 fprintf(f, "%s", move_buffer);
12411 linelen += movelen;
12417 /* Start a new line */
12418 if (linelen > 0) fprintf(f, "\n");
12420 /* Print comments after last move */
12421 if (commentList[i] != NULL) {
12422 fprintf(f, "%s\n", commentList[i]);
12426 if (gameInfo.resultDetails != NULL &&
12427 gameInfo.resultDetails[0] != NULLCHAR) {
12428 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12429 PGNResult(gameInfo.result));
12431 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12435 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12439 /* Save game in old style and close the file */
12441 SaveGameOldStyle(f)
12447 tm = time((time_t *) NULL);
12449 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12452 if (backwardMostMove > 0 || startedFromSetupPosition) {
12453 fprintf(f, "\n[--------------\n");
12454 PrintPosition(f, backwardMostMove);
12455 fprintf(f, "--------------]\n");
12460 i = backwardMostMove;
12461 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12463 while (i < forwardMostMove) {
12464 if (commentList[i] != NULL) {
12465 fprintf(f, "[%s]\n", commentList[i]);
12468 if ((i % 2) == 1) {
12469 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12472 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12474 if (commentList[i] != NULL) {
12478 if (i >= forwardMostMove) {
12482 fprintf(f, "%s\n", parseList[i]);
12487 if (commentList[i] != NULL) {
12488 fprintf(f, "[%s]\n", commentList[i]);
12491 /* This isn't really the old style, but it's close enough */
12492 if (gameInfo.resultDetails != NULL &&
12493 gameInfo.resultDetails[0] != NULLCHAR) {
12494 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12495 gameInfo.resultDetails);
12497 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12504 /* Save the current game to open file f and close the file */
12506 SaveGame(f, dummy, dummy2)
12511 if (gameMode == EditPosition) EditPositionDone(TRUE);
12512 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12513 if (appData.oldSaveStyle)
12514 return SaveGameOldStyle(f);
12516 return SaveGamePGN(f);
12519 /* Save the current position to the given file */
12521 SavePositionToFile(filename)
12527 if (strcmp(filename, "-") == 0) {
12528 return SavePosition(stdout, 0, NULL);
12530 f = fopen(filename, "a");
12532 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12533 DisplayError(buf, errno);
12536 safeStrCpy(buf, lastMsg, MSG_SIZ);
12537 DisplayMessage(_("Waiting for access to save file"), "");
12538 flock(fileno(f), LOCK_EX); // [HGM] lock
12539 DisplayMessage(_("Saving position"), "");
12540 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12541 SavePosition(f, 0, NULL);
12542 DisplayMessage(buf, "");
12548 /* Save the current position to the given open file and close the file */
12550 SavePosition(f, dummy, dummy2)
12558 if (gameMode == EditPosition) EditPositionDone(TRUE);
12559 if (appData.oldSaveStyle) {
12560 tm = time((time_t *) NULL);
12562 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12564 fprintf(f, "[--------------\n");
12565 PrintPosition(f, currentMove);
12566 fprintf(f, "--------------]\n");
12568 fen = PositionToFEN(currentMove, NULL);
12569 fprintf(f, "%s\n", fen);
12577 ReloadCmailMsgEvent(unregister)
12581 static char *inFilename = NULL;
12582 static char *outFilename;
12584 struct stat inbuf, outbuf;
12587 /* Any registered moves are unregistered if unregister is set, */
12588 /* i.e. invoked by the signal handler */
12590 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12591 cmailMoveRegistered[i] = FALSE;
12592 if (cmailCommentList[i] != NULL) {
12593 free(cmailCommentList[i]);
12594 cmailCommentList[i] = NULL;
12597 nCmailMovesRegistered = 0;
12600 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12601 cmailResult[i] = CMAIL_NOT_RESULT;
12605 if (inFilename == NULL) {
12606 /* Because the filenames are static they only get malloced once */
12607 /* and they never get freed */
12608 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12609 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12611 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12612 sprintf(outFilename, "%s.out", appData.cmailGameName);
12615 status = stat(outFilename, &outbuf);
12617 cmailMailedMove = FALSE;
12619 status = stat(inFilename, &inbuf);
12620 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12623 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12624 counts the games, notes how each one terminated, etc.
12626 It would be nice to remove this kludge and instead gather all
12627 the information while building the game list. (And to keep it
12628 in the game list nodes instead of having a bunch of fixed-size
12629 parallel arrays.) Note this will require getting each game's
12630 termination from the PGN tags, as the game list builder does
12631 not process the game moves. --mann
12633 cmailMsgLoaded = TRUE;
12634 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12636 /* Load first game in the file or popup game menu */
12637 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12639 #endif /* !WIN32 */
12647 char string[MSG_SIZ];
12649 if ( cmailMailedMove
12650 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12651 return TRUE; /* Allow free viewing */
12654 /* Unregister move to ensure that we don't leave RegisterMove */
12655 /* with the move registered when the conditions for registering no */
12657 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12658 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12659 nCmailMovesRegistered --;
12661 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12663 free(cmailCommentList[lastLoadGameNumber - 1]);
12664 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12668 if (cmailOldMove == -1) {
12669 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12673 if (currentMove > cmailOldMove + 1) {
12674 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12678 if (currentMove < cmailOldMove) {
12679 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12683 if (forwardMostMove > currentMove) {
12684 /* Silently truncate extra moves */
12688 if ( (currentMove == cmailOldMove + 1)
12689 || ( (currentMove == cmailOldMove)
12690 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12691 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12692 if (gameInfo.result != GameUnfinished) {
12693 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12696 if (commentList[currentMove] != NULL) {
12697 cmailCommentList[lastLoadGameNumber - 1]
12698 = StrSave(commentList[currentMove]);
12700 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12702 if (appData.debugMode)
12703 fprintf(debugFP, "Saving %s for game %d\n",
12704 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12706 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12708 f = fopen(string, "w");
12709 if (appData.oldSaveStyle) {
12710 SaveGameOldStyle(f); /* also closes the file */
12712 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12713 f = fopen(string, "w");
12714 SavePosition(f, 0, NULL); /* also closes the file */
12716 fprintf(f, "{--------------\n");
12717 PrintPosition(f, currentMove);
12718 fprintf(f, "--------------}\n\n");
12720 SaveGame(f, 0, NULL); /* also closes the file*/
12723 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12724 nCmailMovesRegistered ++;
12725 } else if (nCmailGames == 1) {
12726 DisplayError(_("You have not made a move yet"), 0);
12737 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12738 FILE *commandOutput;
12739 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12740 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12746 if (! cmailMsgLoaded) {
12747 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12751 if (nCmailGames == nCmailResults) {
12752 DisplayError(_("No unfinished games"), 0);
12756 #if CMAIL_PROHIBIT_REMAIL
12757 if (cmailMailedMove) {
12758 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);
12759 DisplayError(msg, 0);
12764 if (! (cmailMailedMove || RegisterMove())) return;
12766 if ( cmailMailedMove
12767 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12768 snprintf(string, MSG_SIZ, partCommandString,
12769 appData.debugMode ? " -v" : "", appData.cmailGameName);
12770 commandOutput = popen(string, "r");
12772 if (commandOutput == NULL) {
12773 DisplayError(_("Failed to invoke cmail"), 0);
12775 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12776 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12778 if (nBuffers > 1) {
12779 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12780 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12781 nBytes = MSG_SIZ - 1;
12783 (void) memcpy(msg, buffer, nBytes);
12785 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12787 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12788 cmailMailedMove = TRUE; /* Prevent >1 moves */
12791 for (i = 0; i < nCmailGames; i ++) {
12792 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12797 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12799 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12801 appData.cmailGameName,
12803 LoadGameFromFile(buffer, 1, buffer, FALSE);
12804 cmailMsgLoaded = FALSE;
12808 DisplayInformation(msg);
12809 pclose(commandOutput);
12812 if ((*cmailMsg) != '\0') {
12813 DisplayInformation(cmailMsg);
12818 #endif /* !WIN32 */
12827 int prependComma = 0;
12829 char string[MSG_SIZ]; /* Space for game-list */
12832 if (!cmailMsgLoaded) return "";
12834 if (cmailMailedMove) {
12835 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12837 /* Create a list of games left */
12838 snprintf(string, MSG_SIZ, "[");
12839 for (i = 0; i < nCmailGames; i ++) {
12840 if (! ( cmailMoveRegistered[i]
12841 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12842 if (prependComma) {
12843 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12845 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12849 strcat(string, number);
12852 strcat(string, "]");
12854 if (nCmailMovesRegistered + nCmailResults == 0) {
12855 switch (nCmailGames) {
12857 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12861 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12865 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12870 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12872 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12877 if (nCmailResults == nCmailGames) {
12878 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12880 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12885 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12897 if (gameMode == Training)
12898 SetTrainingModeOff();
12901 cmailMsgLoaded = FALSE;
12902 if (appData.icsActive) {
12903 SendToICS(ics_prefix);
12904 SendToICS("refresh\n");
12914 /* Give up on clean exit */
12918 /* Keep trying for clean exit */
12922 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12924 if (telnetISR != NULL) {
12925 RemoveInputSource(telnetISR);
12927 if (icsPR != NoProc) {
12928 DestroyChildProcess(icsPR, TRUE);
12931 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12932 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12934 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12935 /* make sure this other one finishes before killing it! */
12936 if(endingGame) { int count = 0;
12937 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12938 while(endingGame && count++ < 10) DoSleep(1);
12939 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12942 /* Kill off chess programs */
12943 if (first.pr != NoProc) {
12946 DoSleep( appData.delayBeforeQuit );
12947 SendToProgram("quit\n", &first);
12948 DoSleep( appData.delayAfterQuit );
12949 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12951 if (second.pr != NoProc) {
12952 DoSleep( appData.delayBeforeQuit );
12953 SendToProgram("quit\n", &second);
12954 DoSleep( appData.delayAfterQuit );
12955 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12957 if (first.isr != NULL) {
12958 RemoveInputSource(first.isr);
12960 if (second.isr != NULL) {
12961 RemoveInputSource(second.isr);
12964 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12965 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12967 ShutDownFrontEnd();
12974 if (appData.debugMode)
12975 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12979 if (gameMode == MachinePlaysWhite ||
12980 gameMode == MachinePlaysBlack) {
12983 DisplayBothClocks();
12985 if (gameMode == PlayFromGameFile) {
12986 if (appData.timeDelay >= 0)
12987 AutoPlayGameLoop();
12988 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12989 Reset(FALSE, TRUE);
12990 SendToICS(ics_prefix);
12991 SendToICS("refresh\n");
12992 } else if (currentMove < forwardMostMove) {
12993 ForwardInner(forwardMostMove);
12995 pauseExamInvalid = FALSE;
12997 switch (gameMode) {
13001 pauseExamForwardMostMove = forwardMostMove;
13002 pauseExamInvalid = FALSE;
13005 case IcsPlayingWhite:
13006 case IcsPlayingBlack:
13010 case PlayFromGameFile:
13011 (void) StopLoadGameTimer();
13015 case BeginningOfGame:
13016 if (appData.icsActive) return;
13017 /* else fall through */
13018 case MachinePlaysWhite:
13019 case MachinePlaysBlack:
13020 case TwoMachinesPlay:
13021 if (forwardMostMove == 0)
13022 return; /* don't pause if no one has moved */
13023 if ((gameMode == MachinePlaysWhite &&
13024 !WhiteOnMove(forwardMostMove)) ||
13025 (gameMode == MachinePlaysBlack &&
13026 WhiteOnMove(forwardMostMove))) {
13039 char title[MSG_SIZ];
13041 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13042 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13044 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13045 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13046 parseList[currentMove - 1]);
13049 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13056 char *tags = PGNTags(&gameInfo);
13058 EditTagsPopUp(tags, NULL);
13065 if (appData.noChessProgram || gameMode == AnalyzeMode)
13068 if (gameMode != AnalyzeFile) {
13069 if (!appData.icsEngineAnalyze) {
13071 if (gameMode != EditGame) return;
13073 ResurrectChessProgram();
13074 SendToProgram("analyze\n", &first);
13075 first.analyzing = TRUE;
13076 /*first.maybeThinking = TRUE;*/
13077 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13078 EngineOutputPopUp();
13080 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13085 StartAnalysisClock();
13086 GetTimeMark(&lastNodeCountTime);
13093 if (appData.noChessProgram || gameMode == AnalyzeFile)
13096 if (gameMode != AnalyzeMode) {
13098 if (gameMode != EditGame) return;
13099 ResurrectChessProgram();
13100 SendToProgram("analyze\n", &first);
13101 first.analyzing = TRUE;
13102 /*first.maybeThinking = TRUE;*/
13103 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13104 EngineOutputPopUp();
13106 gameMode = AnalyzeFile;
13111 StartAnalysisClock();
13112 GetTimeMark(&lastNodeCountTime);
13114 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13118 MachineWhiteEvent()
13121 char *bookHit = NULL;
13123 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13127 if (gameMode == PlayFromGameFile ||
13128 gameMode == TwoMachinesPlay ||
13129 gameMode == Training ||
13130 gameMode == AnalyzeMode ||
13131 gameMode == EndOfGame)
13134 if (gameMode == EditPosition)
13135 EditPositionDone(TRUE);
13137 if (!WhiteOnMove(currentMove)) {
13138 DisplayError(_("It is not White's turn"), 0);
13142 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13145 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13146 gameMode == AnalyzeFile)
13149 ResurrectChessProgram(); /* in case it isn't running */
13150 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13151 gameMode = MachinePlaysWhite;
13154 gameMode = MachinePlaysWhite;
13158 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13160 if (first.sendName) {
13161 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13162 SendToProgram(buf, &first);
13164 if (first.sendTime) {
13165 if (first.useColors) {
13166 SendToProgram("black\n", &first); /*gnu kludge*/
13168 SendTimeRemaining(&first, TRUE);
13170 if (first.useColors) {
13171 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13173 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13174 SetMachineThinkingEnables();
13175 first.maybeThinking = TRUE;
13179 if (appData.autoFlipView && !flipView) {
13180 flipView = !flipView;
13181 DrawPosition(FALSE, NULL);
13182 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13185 if(bookHit) { // [HGM] book: simulate book reply
13186 static char bookMove[MSG_SIZ]; // a bit generous?
13188 programStats.nodes = programStats.depth = programStats.time =
13189 programStats.score = programStats.got_only_move = 0;
13190 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13192 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13193 strcat(bookMove, bookHit);
13194 HandleMachineMove(bookMove, &first);
13199 MachineBlackEvent()
13202 char *bookHit = NULL;
13204 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13208 if (gameMode == PlayFromGameFile ||
13209 gameMode == TwoMachinesPlay ||
13210 gameMode == Training ||
13211 gameMode == AnalyzeMode ||
13212 gameMode == EndOfGame)
13215 if (gameMode == EditPosition)
13216 EditPositionDone(TRUE);
13218 if (WhiteOnMove(currentMove)) {
13219 DisplayError(_("It is not Black's turn"), 0);
13223 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13226 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13227 gameMode == AnalyzeFile)
13230 ResurrectChessProgram(); /* in case it isn't running */
13231 gameMode = MachinePlaysBlack;
13235 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13237 if (first.sendName) {
13238 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13239 SendToProgram(buf, &first);
13241 if (first.sendTime) {
13242 if (first.useColors) {
13243 SendToProgram("white\n", &first); /*gnu kludge*/
13245 SendTimeRemaining(&first, FALSE);
13247 if (first.useColors) {
13248 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13250 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13251 SetMachineThinkingEnables();
13252 first.maybeThinking = TRUE;
13255 if (appData.autoFlipView && flipView) {
13256 flipView = !flipView;
13257 DrawPosition(FALSE, NULL);
13258 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13260 if(bookHit) { // [HGM] book: simulate book reply
13261 static char bookMove[MSG_SIZ]; // a bit generous?
13263 programStats.nodes = programStats.depth = programStats.time =
13264 programStats.score = programStats.got_only_move = 0;
13265 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13267 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13268 strcat(bookMove, bookHit);
13269 HandleMachineMove(bookMove, &first);
13275 DisplayTwoMachinesTitle()
13278 if (appData.matchGames > 0) {
13279 if(appData.tourneyFile[0]) {
13280 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13281 gameInfo.white, gameInfo.black,
13282 nextGame+1, appData.matchGames+1,
13283 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13285 if (first.twoMachinesColor[0] == 'w') {
13286 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13287 gameInfo.white, gameInfo.black,
13288 first.matchWins, second.matchWins,
13289 matchGame - 1 - (first.matchWins + second.matchWins));
13291 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13292 gameInfo.white, gameInfo.black,
13293 second.matchWins, first.matchWins,
13294 matchGame - 1 - (first.matchWins + second.matchWins));
13297 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13303 SettingsMenuIfReady()
13305 if (second.lastPing != second.lastPong) {
13306 DisplayMessage("", _("Waiting for second chess program"));
13307 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13311 DisplayMessage("", "");
13312 SettingsPopUp(&second);
13316 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13319 if (cps->pr == NoProc) {
13320 StartChessProgram(cps);
13321 if (cps->protocolVersion == 1) {
13324 /* kludge: allow timeout for initial "feature" command */
13326 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13327 DisplayMessage("", buf);
13328 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13336 TwoMachinesEvent P((void))
13340 ChessProgramState *onmove;
13341 char *bookHit = NULL;
13342 static int stalling = 0;
13346 if (appData.noChessProgram) return;
13348 switch (gameMode) {
13349 case TwoMachinesPlay:
13351 case MachinePlaysWhite:
13352 case MachinePlaysBlack:
13353 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13354 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13358 case BeginningOfGame:
13359 case PlayFromGameFile:
13362 if (gameMode != EditGame) return;
13365 EditPositionDone(TRUE);
13376 // forwardMostMove = currentMove;
13377 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13379 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13381 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13382 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13383 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13387 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13388 SendToProgram("force\n", &second);
13390 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13393 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13394 if(appData.matchPause>10000 || appData.matchPause<10)
13395 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13396 wait = SubtractTimeMarks(&now, &pauseStart);
13397 if(wait < appData.matchPause) {
13398 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13402 DisplayMessage("", "");
13403 if (startedFromSetupPosition) {
13404 SendBoard(&second, backwardMostMove);
13405 if (appData.debugMode) {
13406 fprintf(debugFP, "Two Machines\n");
13409 for (i = backwardMostMove; i < forwardMostMove; i++) {
13410 SendMoveToProgram(i, &second);
13413 gameMode = TwoMachinesPlay;
13415 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13417 DisplayTwoMachinesTitle();
13419 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13424 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13425 SendToProgram(first.computerString, &first);
13426 if (first.sendName) {
13427 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13428 SendToProgram(buf, &first);
13430 SendToProgram(second.computerString, &second);
13431 if (second.sendName) {
13432 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13433 SendToProgram(buf, &second);
13437 if (!first.sendTime || !second.sendTime) {
13438 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13439 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13441 if (onmove->sendTime) {
13442 if (onmove->useColors) {
13443 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13445 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13447 if (onmove->useColors) {
13448 SendToProgram(onmove->twoMachinesColor, onmove);
13450 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13451 // SendToProgram("go\n", onmove);
13452 onmove->maybeThinking = TRUE;
13453 SetMachineThinkingEnables();
13457 if(bookHit) { // [HGM] book: simulate book reply
13458 static char bookMove[MSG_SIZ]; // a bit generous?
13460 programStats.nodes = programStats.depth = programStats.time =
13461 programStats.score = programStats.got_only_move = 0;
13462 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13464 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13465 strcat(bookMove, bookHit);
13466 savedMessage = bookMove; // args for deferred call
13467 savedState = onmove;
13468 ScheduleDelayedEvent(DeferredBookMove, 1);
13475 if (gameMode == Training) {
13476 SetTrainingModeOff();
13477 gameMode = PlayFromGameFile;
13478 DisplayMessage("", _("Training mode off"));
13480 gameMode = Training;
13481 animateTraining = appData.animate;
13483 /* make sure we are not already at the end of the game */
13484 if (currentMove < forwardMostMove) {
13485 SetTrainingModeOn();
13486 DisplayMessage("", _("Training mode on"));
13488 gameMode = PlayFromGameFile;
13489 DisplayError(_("Already at end of game"), 0);
13498 if (!appData.icsActive) return;
13499 switch (gameMode) {
13500 case IcsPlayingWhite:
13501 case IcsPlayingBlack:
13504 case BeginningOfGame:
13512 EditPositionDone(TRUE);
13525 gameMode = IcsIdle;
13536 switch (gameMode) {
13538 SetTrainingModeOff();
13540 case MachinePlaysWhite:
13541 case MachinePlaysBlack:
13542 case BeginningOfGame:
13543 SendToProgram("force\n", &first);
13544 SetUserThinkingEnables();
13546 case PlayFromGameFile:
13547 (void) StopLoadGameTimer();
13548 if (gameFileFP != NULL) {
13553 EditPositionDone(TRUE);
13558 SendToProgram("force\n", &first);
13560 case TwoMachinesPlay:
13561 GameEnds(EndOfFile, NULL, GE_PLAYER);
13562 ResurrectChessProgram();
13563 SetUserThinkingEnables();
13566 ResurrectChessProgram();
13568 case IcsPlayingBlack:
13569 case IcsPlayingWhite:
13570 DisplayError(_("Warning: You are still playing a game"), 0);
13573 DisplayError(_("Warning: You are still observing a game"), 0);
13576 DisplayError(_("Warning: You are still examining a game"), 0);
13587 first.offeredDraw = second.offeredDraw = 0;
13589 if (gameMode == PlayFromGameFile) {
13590 whiteTimeRemaining = timeRemaining[0][currentMove];
13591 blackTimeRemaining = timeRemaining[1][currentMove];
13595 if (gameMode == MachinePlaysWhite ||
13596 gameMode == MachinePlaysBlack ||
13597 gameMode == TwoMachinesPlay ||
13598 gameMode == EndOfGame) {
13599 i = forwardMostMove;
13600 while (i > currentMove) {
13601 SendToProgram("undo\n", &first);
13604 if(!adjustedClock) {
13605 whiteTimeRemaining = timeRemaining[0][currentMove];
13606 blackTimeRemaining = timeRemaining[1][currentMove];
13607 DisplayBothClocks();
13609 if (whiteFlag || blackFlag) {
13610 whiteFlag = blackFlag = 0;
13615 gameMode = EditGame;
13622 EditPositionEvent()
13624 if (gameMode == EditPosition) {
13630 if (gameMode != EditGame) return;
13632 gameMode = EditPosition;
13635 if (currentMove > 0)
13636 CopyBoard(boards[0], boards[currentMove]);
13638 blackPlaysFirst = !WhiteOnMove(currentMove);
13640 currentMove = forwardMostMove = backwardMostMove = 0;
13641 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13648 /* [DM] icsEngineAnalyze - possible call from other functions */
13649 if (appData.icsEngineAnalyze) {
13650 appData.icsEngineAnalyze = FALSE;
13652 DisplayMessage("",_("Close ICS engine analyze..."));
13654 if (first.analysisSupport && first.analyzing) {
13655 SendToProgram("exit\n", &first);
13656 first.analyzing = FALSE;
13658 thinkOutput[0] = NULLCHAR;
13662 EditPositionDone(Boolean fakeRights)
13664 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13666 startedFromSetupPosition = TRUE;
13667 InitChessProgram(&first, FALSE);
13668 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13669 boards[0][EP_STATUS] = EP_NONE;
13670 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13671 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13672 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13673 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13674 } else boards[0][CASTLING][2] = NoRights;
13675 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13676 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13677 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13678 } else boards[0][CASTLING][5] = NoRights;
13680 SendToProgram("force\n", &first);
13681 if (blackPlaysFirst) {
13682 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13683 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13684 currentMove = forwardMostMove = backwardMostMove = 1;
13685 CopyBoard(boards[1], boards[0]);
13687 currentMove = forwardMostMove = backwardMostMove = 0;
13689 SendBoard(&first, forwardMostMove);
13690 if (appData.debugMode) {
13691 fprintf(debugFP, "EditPosDone\n");
13694 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13695 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13696 gameMode = EditGame;
13698 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13699 ClearHighlights(); /* [AS] */
13702 /* Pause for `ms' milliseconds */
13703 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13713 } while (SubtractTimeMarks(&m2, &m1) < ms);
13716 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13718 SendMultiLineToICS(buf)
13721 char temp[MSG_SIZ+1], *p;
13728 strncpy(temp, buf, len);
13733 if (*p == '\n' || *p == '\r')
13738 strcat(temp, "\n");
13740 SendToPlayer(temp, strlen(temp));
13744 SetWhiteToPlayEvent()
13746 if (gameMode == EditPosition) {
13747 blackPlaysFirst = FALSE;
13748 DisplayBothClocks(); /* works because currentMove is 0 */
13749 } else if (gameMode == IcsExamining) {
13750 SendToICS(ics_prefix);
13751 SendToICS("tomove white\n");
13756 SetBlackToPlayEvent()
13758 if (gameMode == EditPosition) {
13759 blackPlaysFirst = TRUE;
13760 currentMove = 1; /* kludge */
13761 DisplayBothClocks();
13763 } else if (gameMode == IcsExamining) {
13764 SendToICS(ics_prefix);
13765 SendToICS("tomove black\n");
13770 EditPositionMenuEvent(selection, x, y)
13771 ChessSquare selection;
13775 ChessSquare piece = boards[0][y][x];
13777 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13779 switch (selection) {
13781 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13782 SendToICS(ics_prefix);
13783 SendToICS("bsetup clear\n");
13784 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13785 SendToICS(ics_prefix);
13786 SendToICS("clearboard\n");
13788 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13789 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13790 for (y = 0; y < BOARD_HEIGHT; y++) {
13791 if (gameMode == IcsExamining) {
13792 if (boards[currentMove][y][x] != EmptySquare) {
13793 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13798 boards[0][y][x] = p;
13803 if (gameMode == EditPosition) {
13804 DrawPosition(FALSE, boards[0]);
13809 SetWhiteToPlayEvent();
13813 SetBlackToPlayEvent();
13817 if (gameMode == IcsExamining) {
13818 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13819 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13822 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13823 if(x == BOARD_LEFT-2) {
13824 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13825 boards[0][y][1] = 0;
13827 if(x == BOARD_RGHT+1) {
13828 if(y >= gameInfo.holdingsSize) break;
13829 boards[0][y][BOARD_WIDTH-2] = 0;
13832 boards[0][y][x] = EmptySquare;
13833 DrawPosition(FALSE, boards[0]);
13838 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13839 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13840 selection = (ChessSquare) (PROMOTED piece);
13841 } else if(piece == EmptySquare) selection = WhiteSilver;
13842 else selection = (ChessSquare)((int)piece - 1);
13846 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13847 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13848 selection = (ChessSquare) (DEMOTED piece);
13849 } else if(piece == EmptySquare) selection = BlackSilver;
13850 else selection = (ChessSquare)((int)piece + 1);
13855 if(gameInfo.variant == VariantShatranj ||
13856 gameInfo.variant == VariantXiangqi ||
13857 gameInfo.variant == VariantCourier ||
13858 gameInfo.variant == VariantMakruk )
13859 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13864 if(gameInfo.variant == VariantXiangqi)
13865 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13866 if(gameInfo.variant == VariantKnightmate)
13867 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13870 if (gameMode == IcsExamining) {
13871 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13872 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13873 PieceToChar(selection), AAA + x, ONE + y);
13876 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13878 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13879 n = PieceToNumber(selection - BlackPawn);
13880 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13881 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13882 boards[0][BOARD_HEIGHT-1-n][1]++;
13884 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13885 n = PieceToNumber(selection);
13886 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13887 boards[0][n][BOARD_WIDTH-1] = selection;
13888 boards[0][n][BOARD_WIDTH-2]++;
13891 boards[0][y][x] = selection;
13892 DrawPosition(TRUE, boards[0]);
13900 DropMenuEvent(selection, x, y)
13901 ChessSquare selection;
13904 ChessMove moveType;
13906 switch (gameMode) {
13907 case IcsPlayingWhite:
13908 case MachinePlaysBlack:
13909 if (!WhiteOnMove(currentMove)) {
13910 DisplayMoveError(_("It is Black's turn"));
13913 moveType = WhiteDrop;
13915 case IcsPlayingBlack:
13916 case MachinePlaysWhite:
13917 if (WhiteOnMove(currentMove)) {
13918 DisplayMoveError(_("It is White's turn"));
13921 moveType = BlackDrop;
13924 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13930 if (moveType == BlackDrop && selection < BlackPawn) {
13931 selection = (ChessSquare) ((int) selection
13932 + (int) BlackPawn - (int) WhitePawn);
13934 if (boards[currentMove][y][x] != EmptySquare) {
13935 DisplayMoveError(_("That square is occupied"));
13939 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13945 /* Accept a pending offer of any kind from opponent */
13947 if (appData.icsActive) {
13948 SendToICS(ics_prefix);
13949 SendToICS("accept\n");
13950 } else if (cmailMsgLoaded) {
13951 if (currentMove == cmailOldMove &&
13952 commentList[cmailOldMove] != NULL &&
13953 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13954 "Black offers a draw" : "White offers a draw")) {
13956 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13957 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13959 DisplayError(_("There is no pending offer on this move"), 0);
13960 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13963 /* Not used for offers from chess program */
13970 /* Decline a pending offer of any kind from opponent */
13972 if (appData.icsActive) {
13973 SendToICS(ics_prefix);
13974 SendToICS("decline\n");
13975 } else if (cmailMsgLoaded) {
13976 if (currentMove == cmailOldMove &&
13977 commentList[cmailOldMove] != NULL &&
13978 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13979 "Black offers a draw" : "White offers a draw")) {
13981 AppendComment(cmailOldMove, "Draw declined", TRUE);
13982 DisplayComment(cmailOldMove - 1, "Draw declined");
13985 DisplayError(_("There is no pending offer on this move"), 0);
13988 /* Not used for offers from chess program */
13995 /* Issue ICS rematch command */
13996 if (appData.icsActive) {
13997 SendToICS(ics_prefix);
13998 SendToICS("rematch\n");
14005 /* Call your opponent's flag (claim a win on time) */
14006 if (appData.icsActive) {
14007 SendToICS(ics_prefix);
14008 SendToICS("flag\n");
14010 switch (gameMode) {
14013 case MachinePlaysWhite:
14016 GameEnds(GameIsDrawn, "Both players ran out of time",
14019 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14021 DisplayError(_("Your opponent is not out of time"), 0);
14024 case MachinePlaysBlack:
14027 GameEnds(GameIsDrawn, "Both players ran out of time",
14030 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14032 DisplayError(_("Your opponent is not out of time"), 0);
14040 ClockClick(int which)
14041 { // [HGM] code moved to back-end from winboard.c
14042 if(which) { // black clock
14043 if (gameMode == EditPosition || gameMode == IcsExamining) {
14044 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14045 SetBlackToPlayEvent();
14046 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14047 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14048 } else if (shiftKey) {
14049 AdjustClock(which, -1);
14050 } else if (gameMode == IcsPlayingWhite ||
14051 gameMode == MachinePlaysBlack) {
14054 } else { // white clock
14055 if (gameMode == EditPosition || gameMode == IcsExamining) {
14056 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14057 SetWhiteToPlayEvent();
14058 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14059 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14060 } else if (shiftKey) {
14061 AdjustClock(which, -1);
14062 } else if (gameMode == IcsPlayingBlack ||
14063 gameMode == MachinePlaysWhite) {
14072 /* Offer draw or accept pending draw offer from opponent */
14074 if (appData.icsActive) {
14075 /* Note: tournament rules require draw offers to be
14076 made after you make your move but before you punch
14077 your clock. Currently ICS doesn't let you do that;
14078 instead, you immediately punch your clock after making
14079 a move, but you can offer a draw at any time. */
14081 SendToICS(ics_prefix);
14082 SendToICS("draw\n");
14083 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14084 } else if (cmailMsgLoaded) {
14085 if (currentMove == cmailOldMove &&
14086 commentList[cmailOldMove] != NULL &&
14087 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14088 "Black offers a draw" : "White offers a draw")) {
14089 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14090 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14091 } else if (currentMove == cmailOldMove + 1) {
14092 char *offer = WhiteOnMove(cmailOldMove) ?
14093 "White offers a draw" : "Black offers a draw";
14094 AppendComment(currentMove, offer, TRUE);
14095 DisplayComment(currentMove - 1, offer);
14096 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14098 DisplayError(_("You must make your move before offering a draw"), 0);
14099 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14101 } else if (first.offeredDraw) {
14102 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14104 if (first.sendDrawOffers) {
14105 SendToProgram("draw\n", &first);
14106 userOfferedDraw = TRUE;
14114 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14116 if (appData.icsActive) {
14117 SendToICS(ics_prefix);
14118 SendToICS("adjourn\n");
14120 /* Currently GNU Chess doesn't offer or accept Adjourns */
14128 /* Offer Abort or accept pending Abort offer from opponent */
14130 if (appData.icsActive) {
14131 SendToICS(ics_prefix);
14132 SendToICS("abort\n");
14134 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14141 /* Resign. You can do this even if it's not your turn. */
14143 if (appData.icsActive) {
14144 SendToICS(ics_prefix);
14145 SendToICS("resign\n");
14147 switch (gameMode) {
14148 case MachinePlaysWhite:
14149 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14151 case MachinePlaysBlack:
14152 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14155 if (cmailMsgLoaded) {
14157 if (WhiteOnMove(cmailOldMove)) {
14158 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14160 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14162 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14173 StopObservingEvent()
14175 /* Stop observing current games */
14176 SendToICS(ics_prefix);
14177 SendToICS("unobserve\n");
14181 StopExaminingEvent()
14183 /* Stop observing current game */
14184 SendToICS(ics_prefix);
14185 SendToICS("unexamine\n");
14189 ForwardInner(target)
14194 if (appData.debugMode)
14195 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14196 target, currentMove, forwardMostMove);
14198 if (gameMode == EditPosition)
14201 if (gameMode == PlayFromGameFile && !pausing)
14204 if (gameMode == IcsExamining && pausing)
14205 limit = pauseExamForwardMostMove;
14207 limit = forwardMostMove;
14209 if (target > limit) target = limit;
14211 if (target > 0 && moveList[target - 1][0]) {
14212 int fromX, fromY, toX, toY;
14213 toX = moveList[target - 1][2] - AAA;
14214 toY = moveList[target - 1][3] - ONE;
14215 if (moveList[target - 1][1] == '@') {
14216 if (appData.highlightLastMove) {
14217 SetHighlights(-1, -1, toX, toY);
14220 fromX = moveList[target - 1][0] - AAA;
14221 fromY = moveList[target - 1][1] - ONE;
14222 if (target == currentMove + 1) {
14223 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14225 if (appData.highlightLastMove) {
14226 SetHighlights(fromX, fromY, toX, toY);
14230 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14231 gameMode == Training || gameMode == PlayFromGameFile ||
14232 gameMode == AnalyzeFile) {
14233 while (currentMove < target) {
14234 SendMoveToProgram(currentMove++, &first);
14237 currentMove = target;
14240 if (gameMode == EditGame || gameMode == EndOfGame) {
14241 whiteTimeRemaining = timeRemaining[0][currentMove];
14242 blackTimeRemaining = timeRemaining[1][currentMove];
14244 DisplayBothClocks();
14245 DisplayMove(currentMove - 1);
14246 DrawPosition(FALSE, boards[currentMove]);
14247 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14248 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14249 DisplayComment(currentMove - 1, commentList[currentMove]);
14257 if (gameMode == IcsExamining && !pausing) {
14258 SendToICS(ics_prefix);
14259 SendToICS("forward\n");
14261 ForwardInner(currentMove + 1);
14268 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14269 /* to optimze, we temporarily turn off analysis mode while we feed
14270 * the remaining moves to the engine. Otherwise we get analysis output
14273 if (first.analysisSupport) {
14274 SendToProgram("exit\nforce\n", &first);
14275 first.analyzing = FALSE;
14279 if (gameMode == IcsExamining && !pausing) {
14280 SendToICS(ics_prefix);
14281 SendToICS("forward 999999\n");
14283 ForwardInner(forwardMostMove);
14286 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14287 /* we have fed all the moves, so reactivate analysis mode */
14288 SendToProgram("analyze\n", &first);
14289 first.analyzing = TRUE;
14290 /*first.maybeThinking = TRUE;*/
14291 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14296 BackwardInner(target)
14299 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14301 if (appData.debugMode)
14302 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14303 target, currentMove, forwardMostMove);
14305 if (gameMode == EditPosition) return;
14306 if (currentMove <= backwardMostMove) {
14308 DrawPosition(full_redraw, boards[currentMove]);
14311 if (gameMode == PlayFromGameFile && !pausing)
14314 if (moveList[target][0]) {
14315 int fromX, fromY, toX, toY;
14316 toX = moveList[target][2] - AAA;
14317 toY = moveList[target][3] - ONE;
14318 if (moveList[target][1] == '@') {
14319 if (appData.highlightLastMove) {
14320 SetHighlights(-1, -1, toX, toY);
14323 fromX = moveList[target][0] - AAA;
14324 fromY = moveList[target][1] - ONE;
14325 if (target == currentMove - 1) {
14326 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14328 if (appData.highlightLastMove) {
14329 SetHighlights(fromX, fromY, toX, toY);
14333 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14334 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14335 while (currentMove > target) {
14336 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14337 // null move cannot be undone. Reload program with move history before it.
14339 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14340 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14342 SendBoard(&first, i);
14343 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14346 SendToProgram("undo\n", &first);
14350 currentMove = target;
14353 if (gameMode == EditGame || gameMode == EndOfGame) {
14354 whiteTimeRemaining = timeRemaining[0][currentMove];
14355 blackTimeRemaining = timeRemaining[1][currentMove];
14357 DisplayBothClocks();
14358 DisplayMove(currentMove - 1);
14359 DrawPosition(full_redraw, boards[currentMove]);
14360 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14361 // [HGM] PV info: routine tests if comment empty
14362 DisplayComment(currentMove - 1, commentList[currentMove]);
14368 if (gameMode == IcsExamining && !pausing) {
14369 SendToICS(ics_prefix);
14370 SendToICS("backward\n");
14372 BackwardInner(currentMove - 1);
14379 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14380 /* to optimize, we temporarily turn off analysis mode while we undo
14381 * all the moves. Otherwise we get analysis output after each undo.
14383 if (first.analysisSupport) {
14384 SendToProgram("exit\nforce\n", &first);
14385 first.analyzing = FALSE;
14389 if (gameMode == IcsExamining && !pausing) {
14390 SendToICS(ics_prefix);
14391 SendToICS("backward 999999\n");
14393 BackwardInner(backwardMostMove);
14396 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14397 /* we have fed all the moves, so reactivate analysis mode */
14398 SendToProgram("analyze\n", &first);
14399 first.analyzing = TRUE;
14400 /*first.maybeThinking = TRUE;*/
14401 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14408 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14409 if (to >= forwardMostMove) to = forwardMostMove;
14410 if (to <= backwardMostMove) to = backwardMostMove;
14411 if (to < currentMove) {
14419 RevertEvent(Boolean annotate)
14421 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14424 if (gameMode != IcsExamining) {
14425 DisplayError(_("You are not examining a game"), 0);
14429 DisplayError(_("You can't revert while pausing"), 0);
14432 SendToICS(ics_prefix);
14433 SendToICS("revert\n");
14439 switch (gameMode) {
14440 case MachinePlaysWhite:
14441 case MachinePlaysBlack:
14442 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14443 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14446 if (forwardMostMove < 2) return;
14447 currentMove = forwardMostMove = forwardMostMove - 2;
14448 whiteTimeRemaining = timeRemaining[0][currentMove];
14449 blackTimeRemaining = timeRemaining[1][currentMove];
14450 DisplayBothClocks();
14451 DisplayMove(currentMove - 1);
14452 ClearHighlights();/*!! could figure this out*/
14453 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14454 SendToProgram("remove\n", &first);
14455 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14458 case BeginningOfGame:
14462 case IcsPlayingWhite:
14463 case IcsPlayingBlack:
14464 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14465 SendToICS(ics_prefix);
14466 SendToICS("takeback 2\n");
14468 SendToICS(ics_prefix);
14469 SendToICS("takeback 1\n");
14478 ChessProgramState *cps;
14480 switch (gameMode) {
14481 case MachinePlaysWhite:
14482 if (!WhiteOnMove(forwardMostMove)) {
14483 DisplayError(_("It is your turn"), 0);
14488 case MachinePlaysBlack:
14489 if (WhiteOnMove(forwardMostMove)) {
14490 DisplayError(_("It is your turn"), 0);
14495 case TwoMachinesPlay:
14496 if (WhiteOnMove(forwardMostMove) ==
14497 (first.twoMachinesColor[0] == 'w')) {
14503 case BeginningOfGame:
14507 SendToProgram("?\n", cps);
14511 TruncateGameEvent()
14514 if (gameMode != EditGame) return;
14521 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14522 if (forwardMostMove > currentMove) {
14523 if (gameInfo.resultDetails != NULL) {
14524 free(gameInfo.resultDetails);
14525 gameInfo.resultDetails = NULL;
14526 gameInfo.result = GameUnfinished;
14528 forwardMostMove = currentMove;
14529 HistorySet(parseList, backwardMostMove, forwardMostMove,
14537 if (appData.noChessProgram) return;
14538 switch (gameMode) {
14539 case MachinePlaysWhite:
14540 if (WhiteOnMove(forwardMostMove)) {
14541 DisplayError(_("Wait until your turn"), 0);
14545 case BeginningOfGame:
14546 case MachinePlaysBlack:
14547 if (!WhiteOnMove(forwardMostMove)) {
14548 DisplayError(_("Wait until your turn"), 0);
14553 DisplayError(_("No hint available"), 0);
14556 SendToProgram("hint\n", &first);
14557 hintRequested = TRUE;
14563 if (appData.noChessProgram) return;
14564 switch (gameMode) {
14565 case MachinePlaysWhite:
14566 if (WhiteOnMove(forwardMostMove)) {
14567 DisplayError(_("Wait until your turn"), 0);
14571 case BeginningOfGame:
14572 case MachinePlaysBlack:
14573 if (!WhiteOnMove(forwardMostMove)) {
14574 DisplayError(_("Wait until your turn"), 0);
14579 EditPositionDone(TRUE);
14581 case TwoMachinesPlay:
14586 SendToProgram("bk\n", &first);
14587 bookOutput[0] = NULLCHAR;
14588 bookRequested = TRUE;
14594 char *tags = PGNTags(&gameInfo);
14595 TagsPopUp(tags, CmailMsg());
14599 /* end button procedures */
14602 PrintPosition(fp, move)
14608 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14609 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14610 char c = PieceToChar(boards[move][i][j]);
14611 fputc(c == 'x' ? '.' : c, fp);
14612 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14615 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14616 fprintf(fp, "white to play\n");
14618 fprintf(fp, "black to play\n");
14625 if (gameInfo.white != NULL) {
14626 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14632 /* Find last component of program's own name, using some heuristics */
14634 TidyProgramName(prog, host, buf)
14635 char *prog, *host, buf[MSG_SIZ];
14638 int local = (strcmp(host, "localhost") == 0);
14639 while (!local && (p = strchr(prog, ';')) != NULL) {
14641 while (*p == ' ') p++;
14644 if (*prog == '"' || *prog == '\'') {
14645 q = strchr(prog + 1, *prog);
14647 q = strchr(prog, ' ');
14649 if (q == NULL) q = prog + strlen(prog);
14651 while (p >= prog && *p != '/' && *p != '\\') p--;
14653 if(p == prog && *p == '"') p++;
14654 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14655 memcpy(buf, p, q - p);
14656 buf[q - p] = NULLCHAR;
14664 TimeControlTagValue()
14667 if (!appData.clockMode) {
14668 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14669 } else if (movesPerSession > 0) {
14670 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14671 } else if (timeIncrement == 0) {
14672 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14674 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14676 return StrSave(buf);
14682 /* This routine is used only for certain modes */
14683 VariantClass v = gameInfo.variant;
14684 ChessMove r = GameUnfinished;
14687 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14688 r = gameInfo.result;
14689 p = gameInfo.resultDetails;
14690 gameInfo.resultDetails = NULL;
14692 ClearGameInfo(&gameInfo);
14693 gameInfo.variant = v;
14695 switch (gameMode) {
14696 case MachinePlaysWhite:
14697 gameInfo.event = StrSave( appData.pgnEventHeader );
14698 gameInfo.site = StrSave(HostName());
14699 gameInfo.date = PGNDate();
14700 gameInfo.round = StrSave("-");
14701 gameInfo.white = StrSave(first.tidy);
14702 gameInfo.black = StrSave(UserName());
14703 gameInfo.timeControl = TimeControlTagValue();
14706 case MachinePlaysBlack:
14707 gameInfo.event = StrSave( appData.pgnEventHeader );
14708 gameInfo.site = StrSave(HostName());
14709 gameInfo.date = PGNDate();
14710 gameInfo.round = StrSave("-");
14711 gameInfo.white = StrSave(UserName());
14712 gameInfo.black = StrSave(first.tidy);
14713 gameInfo.timeControl = TimeControlTagValue();
14716 case TwoMachinesPlay:
14717 gameInfo.event = StrSave( appData.pgnEventHeader );
14718 gameInfo.site = StrSave(HostName());
14719 gameInfo.date = PGNDate();
14722 snprintf(buf, MSG_SIZ, "%d", roundNr);
14723 gameInfo.round = StrSave(buf);
14725 gameInfo.round = StrSave("-");
14727 if (first.twoMachinesColor[0] == 'w') {
14728 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14729 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14731 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14732 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14734 gameInfo.timeControl = TimeControlTagValue();
14738 gameInfo.event = StrSave("Edited game");
14739 gameInfo.site = StrSave(HostName());
14740 gameInfo.date = PGNDate();
14741 gameInfo.round = StrSave("-");
14742 gameInfo.white = StrSave("-");
14743 gameInfo.black = StrSave("-");
14744 gameInfo.result = r;
14745 gameInfo.resultDetails = p;
14749 gameInfo.event = StrSave("Edited position");
14750 gameInfo.site = StrSave(HostName());
14751 gameInfo.date = PGNDate();
14752 gameInfo.round = StrSave("-");
14753 gameInfo.white = StrSave("-");
14754 gameInfo.black = StrSave("-");
14757 case IcsPlayingWhite:
14758 case IcsPlayingBlack:
14763 case PlayFromGameFile:
14764 gameInfo.event = StrSave("Game from non-PGN file");
14765 gameInfo.site = StrSave(HostName());
14766 gameInfo.date = PGNDate();
14767 gameInfo.round = StrSave("-");
14768 gameInfo.white = StrSave("?");
14769 gameInfo.black = StrSave("?");
14778 ReplaceComment(index, text)
14786 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14787 pvInfoList[index-1].depth == len &&
14788 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14789 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14790 while (*text == '\n') text++;
14791 len = strlen(text);
14792 while (len > 0 && text[len - 1] == '\n') len--;
14794 if (commentList[index] != NULL)
14795 free(commentList[index]);
14798 commentList[index] = NULL;
14801 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14802 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14803 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14804 commentList[index] = (char *) malloc(len + 2);
14805 strncpy(commentList[index], text, len);
14806 commentList[index][len] = '\n';
14807 commentList[index][len + 1] = NULLCHAR;
14809 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14811 commentList[index] = (char *) malloc(len + 7);
14812 safeStrCpy(commentList[index], "{\n", 3);
14813 safeStrCpy(commentList[index]+2, text, len+1);
14814 commentList[index][len+2] = NULLCHAR;
14815 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14816 strcat(commentList[index], "\n}\n");
14830 if (ch == '\r') continue;
14832 } while (ch != '\0');
14836 AppendComment(index, text, addBraces)
14839 Boolean addBraces; // [HGM] braces: tells if we should add {}
14844 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14845 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14848 while (*text == '\n') text++;
14849 len = strlen(text);
14850 while (len > 0 && text[len - 1] == '\n') len--;
14852 if (len == 0) return;
14854 if (commentList[index] != NULL) {
14855 Boolean addClosingBrace = addBraces;
14856 old = commentList[index];
14857 oldlen = strlen(old);
14858 while(commentList[index][oldlen-1] == '\n')
14859 commentList[index][--oldlen] = NULLCHAR;
14860 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14861 safeStrCpy(commentList[index], old, oldlen + len + 6);
14863 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14864 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14865 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14866 while (*text == '\n') { text++; len--; }
14867 commentList[index][--oldlen] = NULLCHAR;
14869 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14870 else strcat(commentList[index], "\n");
14871 strcat(commentList[index], text);
14872 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14873 else strcat(commentList[index], "\n");
14875 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14877 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14878 else commentList[index][0] = NULLCHAR;
14879 strcat(commentList[index], text);
14880 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14881 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14885 static char * FindStr( char * text, char * sub_text )
14887 char * result = strstr( text, sub_text );
14889 if( result != NULL ) {
14890 result += strlen( sub_text );
14896 /* [AS] Try to extract PV info from PGN comment */
14897 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14898 char *GetInfoFromComment( int index, char * text )
14900 char * sep = text, *p;
14902 if( text != NULL && index > 0 ) {
14905 int time = -1, sec = 0, deci;
14906 char * s_eval = FindStr( text, "[%eval " );
14907 char * s_emt = FindStr( text, "[%emt " );
14909 if( s_eval != NULL || s_emt != NULL ) {
14913 if( s_eval != NULL ) {
14914 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14918 if( delim != ']' ) {
14923 if( s_emt != NULL ) {
14928 /* We expect something like: [+|-]nnn.nn/dd */
14931 if(*text != '{') return text; // [HGM] braces: must be normal comment
14933 sep = strchr( text, '/' );
14934 if( sep == NULL || sep < (text+4) ) {
14939 if(p[1] == '(') { // comment starts with PV
14940 p = strchr(p, ')'); // locate end of PV
14941 if(p == NULL || sep < p+5) return text;
14942 // at this point we have something like "{(.*) +0.23/6 ..."
14943 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14944 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14945 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14947 time = -1; sec = -1; deci = -1;
14948 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14949 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14950 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14951 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14955 if( score_lo < 0 || score_lo >= 100 ) {
14959 if(sec >= 0) time = 600*time + 10*sec; else
14960 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14962 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14964 /* [HGM] PV time: now locate end of PV info */
14965 while( *++sep >= '0' && *sep <= '9'); // strip depth
14967 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14969 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14971 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14972 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14983 pvInfoList[index-1].depth = depth;
14984 pvInfoList[index-1].score = score;
14985 pvInfoList[index-1].time = 10*time; // centi-sec
14986 if(*sep == '}') *sep = 0; else *--sep = '{';
14987 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14993 SendToProgram(message, cps)
14995 ChessProgramState *cps;
14997 int count, outCount, error;
15000 if (cps->pr == NoProc) return;
15003 if (appData.debugMode) {
15006 fprintf(debugFP, "%ld >%-6s: %s",
15007 SubtractTimeMarks(&now, &programStartTime),
15008 cps->which, message);
15011 count = strlen(message);
15012 outCount = OutputToProcess(cps->pr, message, count, &error);
15013 if (outCount < count && !exiting
15014 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15015 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15016 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15017 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15018 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15019 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15020 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15021 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15023 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15024 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15025 gameInfo.result = res;
15027 gameInfo.resultDetails = StrSave(buf);
15029 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15030 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15035 ReceiveFromProgram(isr, closure, message, count, error)
15036 InputSourceRef isr;
15044 ChessProgramState *cps = (ChessProgramState *)closure;
15046 if (isr != cps->isr) return; /* Killed intentionally */
15049 RemoveInputSource(cps->isr);
15050 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15051 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15052 _(cps->which), cps->program);
15053 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15054 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15055 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15056 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15057 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15059 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15060 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15061 gameInfo.result = res;
15063 gameInfo.resultDetails = StrSave(buf);
15065 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15066 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15068 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15069 _(cps->which), cps->program);
15070 RemoveInputSource(cps->isr);
15072 /* [AS] Program is misbehaving badly... kill it */
15073 if( count == -2 ) {
15074 DestroyChildProcess( cps->pr, 9 );
15078 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15083 if ((end_str = strchr(message, '\r')) != NULL)
15084 *end_str = NULLCHAR;
15085 if ((end_str = strchr(message, '\n')) != NULL)
15086 *end_str = NULLCHAR;
15088 if (appData.debugMode) {
15089 TimeMark now; int print = 1;
15090 char *quote = ""; char c; int i;
15092 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15093 char start = message[0];
15094 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15095 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15096 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15097 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15098 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15099 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15100 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15101 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15102 sscanf(message, "hint: %c", &c)!=1 &&
15103 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15104 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15105 print = (appData.engineComments >= 2);
15107 message[0] = start; // restore original message
15111 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15112 SubtractTimeMarks(&now, &programStartTime), cps->which,
15118 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15119 if (appData.icsEngineAnalyze) {
15120 if (strstr(message, "whisper") != NULL ||
15121 strstr(message, "kibitz") != NULL ||
15122 strstr(message, "tellics") != NULL) return;
15125 HandleMachineMove(message, cps);
15130 SendTimeControl(cps, mps, tc, inc, sd, st)
15131 ChessProgramState *cps;
15132 int mps, inc, sd, st;
15138 if( timeControl_2 > 0 ) {
15139 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15140 tc = timeControl_2;
15143 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15144 inc /= cps->timeOdds;
15145 st /= cps->timeOdds;
15147 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15150 /* Set exact time per move, normally using st command */
15151 if (cps->stKludge) {
15152 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15154 if (seconds == 0) {
15155 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15157 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15160 snprintf(buf, MSG_SIZ, "st %d\n", st);
15163 /* Set conventional or incremental time control, using level command */
15164 if (seconds == 0) {
15165 /* Note old gnuchess bug -- minutes:seconds used to not work.
15166 Fixed in later versions, but still avoid :seconds
15167 when seconds is 0. */
15168 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15170 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15171 seconds, inc/1000.);
15174 SendToProgram(buf, cps);
15176 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15177 /* Orthogonally, limit search to given depth */
15179 if (cps->sdKludge) {
15180 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15182 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15184 SendToProgram(buf, cps);
15187 if(cps->nps >= 0) { /* [HGM] nps */
15188 if(cps->supportsNPS == FALSE)
15189 cps->nps = -1; // don't use if engine explicitly says not supported!
15191 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15192 SendToProgram(buf, cps);
15197 ChessProgramState *WhitePlayer()
15198 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15200 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15201 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15207 SendTimeRemaining(cps, machineWhite)
15208 ChessProgramState *cps;
15209 int /*boolean*/ machineWhite;
15211 char message[MSG_SIZ];
15214 /* Note: this routine must be called when the clocks are stopped
15215 or when they have *just* been set or switched; otherwise
15216 it will be off by the time since the current tick started.
15218 if (machineWhite) {
15219 time = whiteTimeRemaining / 10;
15220 otime = blackTimeRemaining / 10;
15222 time = blackTimeRemaining / 10;
15223 otime = whiteTimeRemaining / 10;
15225 /* [HGM] translate opponent's time by time-odds factor */
15226 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15227 if (appData.debugMode) {
15228 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15231 if (time <= 0) time = 1;
15232 if (otime <= 0) otime = 1;
15234 snprintf(message, MSG_SIZ, "time %ld\n", time);
15235 SendToProgram(message, cps);
15237 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15238 SendToProgram(message, cps);
15242 BoolFeature(p, name, loc, cps)
15246 ChessProgramState *cps;
15249 int len = strlen(name);
15252 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15254 sscanf(*p, "%d", &val);
15256 while (**p && **p != ' ')
15258 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15259 SendToProgram(buf, cps);
15266 IntFeature(p, name, loc, cps)
15270 ChessProgramState *cps;
15273 int len = strlen(name);
15274 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15276 sscanf(*p, "%d", loc);
15277 while (**p && **p != ' ') (*p)++;
15278 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15279 SendToProgram(buf, cps);
15286 StringFeature(p, name, loc, cps)
15290 ChessProgramState *cps;
15293 int len = strlen(name);
15294 if (strncmp((*p), name, len) == 0
15295 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15297 sscanf(*p, "%[^\"]", loc);
15298 while (**p && **p != '\"') (*p)++;
15299 if (**p == '\"') (*p)++;
15300 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15301 SendToProgram(buf, cps);
15308 ParseOption(Option *opt, ChessProgramState *cps)
15309 // [HGM] options: process the string that defines an engine option, and determine
15310 // name, type, default value, and allowed value range
15312 char *p, *q, buf[MSG_SIZ];
15313 int n, min = (-1)<<31, max = 1<<31, def;
15315 if(p = strstr(opt->name, " -spin ")) {
15316 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15317 if(max < min) max = min; // enforce consistency
15318 if(def < min) def = min;
15319 if(def > max) def = max;
15324 } else if((p = strstr(opt->name, " -slider "))) {
15325 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15326 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15327 if(max < min) max = min; // enforce consistency
15328 if(def < min) def = min;
15329 if(def > max) def = max;
15333 opt->type = Spin; // Slider;
15334 } else if((p = strstr(opt->name, " -string "))) {
15335 opt->textValue = p+9;
15336 opt->type = TextBox;
15337 } else if((p = strstr(opt->name, " -file "))) {
15338 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15339 opt->textValue = p+7;
15340 opt->type = FileName; // FileName;
15341 } else if((p = strstr(opt->name, " -path "))) {
15342 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15343 opt->textValue = p+7;
15344 opt->type = PathName; // PathName;
15345 } else if(p = strstr(opt->name, " -check ")) {
15346 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15347 opt->value = (def != 0);
15348 opt->type = CheckBox;
15349 } else if(p = strstr(opt->name, " -combo ")) {
15350 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15351 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15352 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15353 opt->value = n = 0;
15354 while(q = StrStr(q, " /// ")) {
15355 n++; *q = 0; // count choices, and null-terminate each of them
15357 if(*q == '*') { // remember default, which is marked with * prefix
15361 cps->comboList[cps->comboCnt++] = q;
15363 cps->comboList[cps->comboCnt++] = NULL;
15365 opt->type = ComboBox;
15366 } else if(p = strstr(opt->name, " -button")) {
15367 opt->type = Button;
15368 } else if(p = strstr(opt->name, " -save")) {
15369 opt->type = SaveButton;
15370 } else return FALSE;
15371 *p = 0; // terminate option name
15372 // now look if the command-line options define a setting for this engine option.
15373 if(cps->optionSettings && cps->optionSettings[0])
15374 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15375 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15376 snprintf(buf, MSG_SIZ, "option %s", p);
15377 if(p = strstr(buf, ",")) *p = 0;
15378 if(q = strchr(buf, '=')) switch(opt->type) {
15380 for(n=0; n<opt->max; n++)
15381 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15384 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15388 opt->value = atoi(q+1);
15393 SendToProgram(buf, cps);
15399 FeatureDone(cps, val)
15400 ChessProgramState* cps;
15403 DelayedEventCallback cb = GetDelayedEvent();
15404 if ((cb == InitBackEnd3 && cps == &first) ||
15405 (cb == SettingsMenuIfReady && cps == &second) ||
15406 (cb == LoadEngine) ||
15407 (cb == TwoMachinesEventIfReady)) {
15408 CancelDelayedEvent();
15409 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15411 cps->initDone = val;
15414 /* Parse feature command from engine */
15416 ParseFeatures(args, cps)
15418 ChessProgramState *cps;
15426 while (*p == ' ') p++;
15427 if (*p == NULLCHAR) return;
15429 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15430 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15431 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15432 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15433 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15434 if (BoolFeature(&p, "reuse", &val, cps)) {
15435 /* Engine can disable reuse, but can't enable it if user said no */
15436 if (!val) cps->reuse = FALSE;
15439 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15440 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15441 if (gameMode == TwoMachinesPlay) {
15442 DisplayTwoMachinesTitle();
15448 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15449 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15450 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15451 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15452 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15453 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15454 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15455 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15456 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15457 if (IntFeature(&p, "done", &val, cps)) {
15458 FeatureDone(cps, val);
15461 /* Added by Tord: */
15462 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15463 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15464 /* End of additions by Tord */
15466 /* [HGM] added features: */
15467 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15468 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15469 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15470 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15471 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15472 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15473 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15474 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15475 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15476 SendToProgram(buf, cps);
15479 if(cps->nrOptions >= MAX_OPTIONS) {
15481 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15482 DisplayError(buf, 0);
15486 /* End of additions by HGM */
15488 /* unknown feature: complain and skip */
15490 while (*q && *q != '=') q++;
15491 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15492 SendToProgram(buf, cps);
15498 while (*p && *p != '\"') p++;
15499 if (*p == '\"') p++;
15501 while (*p && *p != ' ') p++;
15509 PeriodicUpdatesEvent(newState)
15512 if (newState == appData.periodicUpdates)
15515 appData.periodicUpdates=newState;
15517 /* Display type changes, so update it now */
15518 // DisplayAnalysis();
15520 /* Get the ball rolling again... */
15522 AnalysisPeriodicEvent(1);
15523 StartAnalysisClock();
15528 PonderNextMoveEvent(newState)
15531 if (newState == appData.ponderNextMove) return;
15532 if (gameMode == EditPosition) EditPositionDone(TRUE);
15534 SendToProgram("hard\n", &first);
15535 if (gameMode == TwoMachinesPlay) {
15536 SendToProgram("hard\n", &second);
15539 SendToProgram("easy\n", &first);
15540 thinkOutput[0] = NULLCHAR;
15541 if (gameMode == TwoMachinesPlay) {
15542 SendToProgram("easy\n", &second);
15545 appData.ponderNextMove = newState;
15549 NewSettingEvent(option, feature, command, value)
15551 int option, value, *feature;
15555 if (gameMode == EditPosition) EditPositionDone(TRUE);
15556 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15557 if(feature == NULL || *feature) SendToProgram(buf, &first);
15558 if (gameMode == TwoMachinesPlay) {
15559 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15564 ShowThinkingEvent()
15565 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15567 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15568 int newState = appData.showThinking
15569 // [HGM] thinking: other features now need thinking output as well
15570 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15572 if (oldState == newState) return;
15573 oldState = newState;
15574 if (gameMode == EditPosition) EditPositionDone(TRUE);
15576 SendToProgram("post\n", &first);
15577 if (gameMode == TwoMachinesPlay) {
15578 SendToProgram("post\n", &second);
15581 SendToProgram("nopost\n", &first);
15582 thinkOutput[0] = NULLCHAR;
15583 if (gameMode == TwoMachinesPlay) {
15584 SendToProgram("nopost\n", &second);
15587 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15591 AskQuestionEvent(title, question, replyPrefix, which)
15592 char *title; char *question; char *replyPrefix; char *which;
15594 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15595 if (pr == NoProc) return;
15596 AskQuestion(title, question, replyPrefix, pr);
15600 TypeInEvent(char firstChar)
15602 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15603 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15604 gameMode == AnalyzeMode || gameMode == EditGame ||
15605 gameMode == EditPosition || gameMode == IcsExamining ||
15606 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15607 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15608 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15609 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15610 gameMode == Training) PopUpMoveDialog(firstChar);
15614 TypeInDoneEvent(char *move)
15617 int n, fromX, fromY, toX, toY;
15619 ChessMove moveType;
15622 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15623 EditPositionPasteFEN(move);
15626 // [HGM] movenum: allow move number to be typed in any mode
15627 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15632 if (gameMode != EditGame && currentMove != forwardMostMove &&
15633 gameMode != Training) {
15634 DisplayMoveError(_("Displayed move is not current"));
15636 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15637 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15638 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15639 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15640 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15641 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15643 DisplayMoveError(_("Could not parse move"));
15649 DisplayMove(moveNumber)
15652 char message[MSG_SIZ];
15654 char cpThinkOutput[MSG_SIZ];
15656 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15658 if (moveNumber == forwardMostMove - 1 ||
15659 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15661 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15663 if (strchr(cpThinkOutput, '\n')) {
15664 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15667 *cpThinkOutput = NULLCHAR;
15670 /* [AS] Hide thinking from human user */
15671 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15672 *cpThinkOutput = NULLCHAR;
15673 if( thinkOutput[0] != NULLCHAR ) {
15676 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15677 cpThinkOutput[i] = '.';
15679 cpThinkOutput[i] = NULLCHAR;
15680 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15684 if (moveNumber == forwardMostMove - 1 &&
15685 gameInfo.resultDetails != NULL) {
15686 if (gameInfo.resultDetails[0] == NULLCHAR) {
15687 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15689 snprintf(res, MSG_SIZ, " {%s} %s",
15690 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15696 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15697 DisplayMessage(res, cpThinkOutput);
15699 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15700 WhiteOnMove(moveNumber) ? " " : ".. ",
15701 parseList[moveNumber], res);
15702 DisplayMessage(message, cpThinkOutput);
15707 DisplayComment(moveNumber, text)
15711 char title[MSG_SIZ];
15713 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15714 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15716 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15717 WhiteOnMove(moveNumber) ? " " : ".. ",
15718 parseList[moveNumber]);
15720 if (text != NULL && (appData.autoDisplayComment || commentUp))
15721 CommentPopUp(title, text);
15724 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15725 * might be busy thinking or pondering. It can be omitted if your
15726 * gnuchess is configured to stop thinking immediately on any user
15727 * input. However, that gnuchess feature depends on the FIONREAD
15728 * ioctl, which does not work properly on some flavors of Unix.
15732 ChessProgramState *cps;
15735 if (!cps->useSigint) return;
15736 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15737 switch (gameMode) {
15738 case MachinePlaysWhite:
15739 case MachinePlaysBlack:
15740 case TwoMachinesPlay:
15741 case IcsPlayingWhite:
15742 case IcsPlayingBlack:
15745 /* Skip if we know it isn't thinking */
15746 if (!cps->maybeThinking) return;
15747 if (appData.debugMode)
15748 fprintf(debugFP, "Interrupting %s\n", cps->which);
15749 InterruptChildProcess(cps->pr);
15750 cps->maybeThinking = FALSE;
15755 #endif /*ATTENTION*/
15761 if (whiteTimeRemaining <= 0) {
15764 if (appData.icsActive) {
15765 if (appData.autoCallFlag &&
15766 gameMode == IcsPlayingBlack && !blackFlag) {
15767 SendToICS(ics_prefix);
15768 SendToICS("flag\n");
15772 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15774 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15775 if (appData.autoCallFlag) {
15776 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15783 if (blackTimeRemaining <= 0) {
15786 if (appData.icsActive) {
15787 if (appData.autoCallFlag &&
15788 gameMode == IcsPlayingWhite && !whiteFlag) {
15789 SendToICS(ics_prefix);
15790 SendToICS("flag\n");
15794 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15796 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15797 if (appData.autoCallFlag) {
15798 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15811 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15812 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15815 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15817 if ( !WhiteOnMove(forwardMostMove) ) {
15818 /* White made time control */
15819 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15820 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15821 /* [HGM] time odds: correct new time quota for time odds! */
15822 / WhitePlayer()->timeOdds;
15823 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15825 lastBlack -= blackTimeRemaining;
15826 /* Black made time control */
15827 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15828 / WhitePlayer()->other->timeOdds;
15829 lastWhite = whiteTimeRemaining;
15834 DisplayBothClocks()
15836 int wom = gameMode == EditPosition ?
15837 !blackPlaysFirst : WhiteOnMove(currentMove);
15838 DisplayWhiteClock(whiteTimeRemaining, wom);
15839 DisplayBlackClock(blackTimeRemaining, !wom);
15843 /* Timekeeping seems to be a portability nightmare. I think everyone
15844 has ftime(), but I'm really not sure, so I'm including some ifdefs
15845 to use other calls if you don't. Clocks will be less accurate if
15846 you have neither ftime nor gettimeofday.
15849 /* VS 2008 requires the #include outside of the function */
15850 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15851 #include <sys/timeb.h>
15854 /* Get the current time as a TimeMark */
15859 #if HAVE_GETTIMEOFDAY
15861 struct timeval timeVal;
15862 struct timezone timeZone;
15864 gettimeofday(&timeVal, &timeZone);
15865 tm->sec = (long) timeVal.tv_sec;
15866 tm->ms = (int) (timeVal.tv_usec / 1000L);
15868 #else /*!HAVE_GETTIMEOFDAY*/
15871 // include <sys/timeb.h> / moved to just above start of function
15872 struct timeb timeB;
15875 tm->sec = (long) timeB.time;
15876 tm->ms = (int) timeB.millitm;
15878 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15879 tm->sec = (long) time(NULL);
15885 /* Return the difference in milliseconds between two
15886 time marks. We assume the difference will fit in a long!
15889 SubtractTimeMarks(tm2, tm1)
15890 TimeMark *tm2, *tm1;
15892 return 1000L*(tm2->sec - tm1->sec) +
15893 (long) (tm2->ms - tm1->ms);
15898 * Code to manage the game clocks.
15900 * In tournament play, black starts the clock and then white makes a move.
15901 * We give the human user a slight advantage if he is playing white---the
15902 * clocks don't run until he makes his first move, so it takes zero time.
15903 * Also, we don't account for network lag, so we could get out of sync
15904 * with GNU Chess's clock -- but then, referees are always right.
15907 static TimeMark tickStartTM;
15908 static long intendedTickLength;
15911 NextTickLength(timeRemaining)
15912 long timeRemaining;
15914 long nominalTickLength, nextTickLength;
15916 if (timeRemaining > 0L && timeRemaining <= 10000L)
15917 nominalTickLength = 100L;
15919 nominalTickLength = 1000L;
15920 nextTickLength = timeRemaining % nominalTickLength;
15921 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15923 return nextTickLength;
15926 /* Adjust clock one minute up or down */
15928 AdjustClock(Boolean which, int dir)
15930 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15931 if(which) blackTimeRemaining += 60000*dir;
15932 else whiteTimeRemaining += 60000*dir;
15933 DisplayBothClocks();
15934 adjustedClock = TRUE;
15937 /* Stop clocks and reset to a fresh time control */
15941 (void) StopClockTimer();
15942 if (appData.icsActive) {
15943 whiteTimeRemaining = blackTimeRemaining = 0;
15944 } else if (searchTime) {
15945 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15946 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15947 } else { /* [HGM] correct new time quote for time odds */
15948 whiteTC = blackTC = fullTimeControlString;
15949 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15950 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15952 if (whiteFlag || blackFlag) {
15954 whiteFlag = blackFlag = FALSE;
15956 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15957 DisplayBothClocks();
15958 adjustedClock = FALSE;
15961 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15963 /* Decrement running clock by amount of time that has passed */
15967 long timeRemaining;
15968 long lastTickLength, fudge;
15971 if (!appData.clockMode) return;
15972 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15976 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15978 /* Fudge if we woke up a little too soon */
15979 fudge = intendedTickLength - lastTickLength;
15980 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15982 if (WhiteOnMove(forwardMostMove)) {
15983 if(whiteNPS >= 0) lastTickLength = 0;
15984 timeRemaining = whiteTimeRemaining -= lastTickLength;
15985 if(timeRemaining < 0 && !appData.icsActive) {
15986 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15987 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15988 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15989 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15992 DisplayWhiteClock(whiteTimeRemaining - fudge,
15993 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15995 if(blackNPS >= 0) lastTickLength = 0;
15996 timeRemaining = blackTimeRemaining -= lastTickLength;
15997 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15998 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16000 blackStartMove = forwardMostMove;
16001 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16004 DisplayBlackClock(blackTimeRemaining - fudge,
16005 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16007 if (CheckFlags()) return;
16010 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16011 StartClockTimer(intendedTickLength);
16013 /* if the time remaining has fallen below the alarm threshold, sound the
16014 * alarm. if the alarm has sounded and (due to a takeback or time control
16015 * with increment) the time remaining has increased to a level above the
16016 * threshold, reset the alarm so it can sound again.
16019 if (appData.icsActive && appData.icsAlarm) {
16021 /* make sure we are dealing with the user's clock */
16022 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16023 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16026 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16027 alarmSounded = FALSE;
16028 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16030 alarmSounded = TRUE;
16036 /* A player has just moved, so stop the previously running
16037 clock and (if in clock mode) start the other one.
16038 We redisplay both clocks in case we're in ICS mode, because
16039 ICS gives us an update to both clocks after every move.
16040 Note that this routine is called *after* forwardMostMove
16041 is updated, so the last fractional tick must be subtracted
16042 from the color that is *not* on move now.
16045 SwitchClocks(int newMoveNr)
16047 long lastTickLength;
16049 int flagged = FALSE;
16053 if (StopClockTimer() && appData.clockMode) {
16054 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16055 if (!WhiteOnMove(forwardMostMove)) {
16056 if(blackNPS >= 0) lastTickLength = 0;
16057 blackTimeRemaining -= lastTickLength;
16058 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16059 // if(pvInfoList[forwardMostMove].time == -1)
16060 pvInfoList[forwardMostMove].time = // use GUI time
16061 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16063 if(whiteNPS >= 0) lastTickLength = 0;
16064 whiteTimeRemaining -= lastTickLength;
16065 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16066 // if(pvInfoList[forwardMostMove].time == -1)
16067 pvInfoList[forwardMostMove].time =
16068 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16070 flagged = CheckFlags();
16072 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16073 CheckTimeControl();
16075 if (flagged || !appData.clockMode) return;
16077 switch (gameMode) {
16078 case MachinePlaysBlack:
16079 case MachinePlaysWhite:
16080 case BeginningOfGame:
16081 if (pausing) return;
16085 case PlayFromGameFile:
16093 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16094 if(WhiteOnMove(forwardMostMove))
16095 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16096 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16100 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16101 whiteTimeRemaining : blackTimeRemaining);
16102 StartClockTimer(intendedTickLength);
16106 /* Stop both clocks */
16110 long lastTickLength;
16113 if (!StopClockTimer()) return;
16114 if (!appData.clockMode) return;
16118 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16119 if (WhiteOnMove(forwardMostMove)) {
16120 if(whiteNPS >= 0) lastTickLength = 0;
16121 whiteTimeRemaining -= lastTickLength;
16122 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16124 if(blackNPS >= 0) lastTickLength = 0;
16125 blackTimeRemaining -= lastTickLength;
16126 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16131 /* Start clock of player on move. Time may have been reset, so
16132 if clock is already running, stop and restart it. */
16136 (void) StopClockTimer(); /* in case it was running already */
16137 DisplayBothClocks();
16138 if (CheckFlags()) return;
16140 if (!appData.clockMode) return;
16141 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16143 GetTimeMark(&tickStartTM);
16144 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16145 whiteTimeRemaining : blackTimeRemaining);
16147 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16148 whiteNPS = blackNPS = -1;
16149 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16150 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16151 whiteNPS = first.nps;
16152 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16153 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16154 blackNPS = first.nps;
16155 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16156 whiteNPS = second.nps;
16157 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16158 blackNPS = second.nps;
16159 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16161 StartClockTimer(intendedTickLength);
16168 long second, minute, hour, day;
16170 static char buf[32];
16172 if (ms > 0 && ms <= 9900) {
16173 /* convert milliseconds to tenths, rounding up */
16174 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16176 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16180 /* convert milliseconds to seconds, rounding up */
16181 /* use floating point to avoid strangeness of integer division
16182 with negative dividends on many machines */
16183 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16190 day = second / (60 * 60 * 24);
16191 second = second % (60 * 60 * 24);
16192 hour = second / (60 * 60);
16193 second = second % (60 * 60);
16194 minute = second / 60;
16195 second = second % 60;
16198 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16199 sign, day, hour, minute, second);
16201 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16203 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16210 * This is necessary because some C libraries aren't ANSI C compliant yet.
16213 StrStr(string, match)
16214 char *string, *match;
16218 length = strlen(match);
16220 for (i = strlen(string) - length; i >= 0; i--, string++)
16221 if (!strncmp(match, string, length))
16228 StrCaseStr(string, match)
16229 char *string, *match;
16233 length = strlen(match);
16235 for (i = strlen(string) - length; i >= 0; i--, string++) {
16236 for (j = 0; j < length; j++) {
16237 if (ToLower(match[j]) != ToLower(string[j]))
16240 if (j == length) return string;
16254 c1 = ToLower(*s1++);
16255 c2 = ToLower(*s2++);
16256 if (c1 > c2) return 1;
16257 if (c1 < c2) return -1;
16258 if (c1 == NULLCHAR) return 0;
16267 return isupper(c) ? tolower(c) : c;
16275 return islower(c) ? toupper(c) : c;
16277 #endif /* !_amigados */
16285 if ((ret = (char *) malloc(strlen(s) + 1)))
16287 safeStrCpy(ret, s, strlen(s)+1);
16293 StrSavePtr(s, savePtr)
16294 char *s, **savePtr;
16299 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16300 safeStrCpy(*savePtr, s, strlen(s)+1);
16312 clock = time((time_t *)NULL);
16313 tm = localtime(&clock);
16314 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16315 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16316 return StrSave(buf);
16321 PositionToFEN(move, overrideCastling)
16323 char *overrideCastling;
16325 int i, j, fromX, fromY, toX, toY;
16332 whiteToPlay = (gameMode == EditPosition) ?
16333 !blackPlaysFirst : (move % 2 == 0);
16336 /* Piece placement data */
16337 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16338 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16340 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16341 if (boards[move][i][j] == EmptySquare) {
16343 } else { ChessSquare piece = boards[move][i][j];
16344 if (emptycount > 0) {
16345 if(emptycount<10) /* [HGM] can be >= 10 */
16346 *p++ = '0' + emptycount;
16347 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16350 if(PieceToChar(piece) == '+') {
16351 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16353 piece = (ChessSquare)(DEMOTED piece);
16355 *p++ = PieceToChar(piece);
16357 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16358 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16363 if (emptycount > 0) {
16364 if(emptycount<10) /* [HGM] can be >= 10 */
16365 *p++ = '0' + emptycount;
16366 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16373 /* [HGM] print Crazyhouse or Shogi holdings */
16374 if( gameInfo.holdingsWidth ) {
16375 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16377 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16378 piece = boards[move][i][BOARD_WIDTH-1];
16379 if( piece != EmptySquare )
16380 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16381 *p++ = PieceToChar(piece);
16383 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16384 piece = boards[move][BOARD_HEIGHT-i-1][0];
16385 if( piece != EmptySquare )
16386 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16387 *p++ = PieceToChar(piece);
16390 if( q == p ) *p++ = '-';
16396 *p++ = whiteToPlay ? 'w' : 'b';
16399 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16400 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16402 if(nrCastlingRights) {
16404 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16405 /* [HGM] write directly from rights */
16406 if(boards[move][CASTLING][2] != NoRights &&
16407 boards[move][CASTLING][0] != NoRights )
16408 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16409 if(boards[move][CASTLING][2] != NoRights &&
16410 boards[move][CASTLING][1] != NoRights )
16411 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16412 if(boards[move][CASTLING][5] != NoRights &&
16413 boards[move][CASTLING][3] != NoRights )
16414 *p++ = boards[move][CASTLING][3] + AAA;
16415 if(boards[move][CASTLING][5] != NoRights &&
16416 boards[move][CASTLING][4] != NoRights )
16417 *p++ = boards[move][CASTLING][4] + AAA;
16420 /* [HGM] write true castling rights */
16421 if( nrCastlingRights == 6 ) {
16422 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16423 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16424 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16425 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16426 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16427 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16428 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16429 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16432 if (q == p) *p++ = '-'; /* No castling rights */
16436 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16437 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16438 /* En passant target square */
16439 if (move > backwardMostMove) {
16440 fromX = moveList[move - 1][0] - AAA;
16441 fromY = moveList[move - 1][1] - ONE;
16442 toX = moveList[move - 1][2] - AAA;
16443 toY = moveList[move - 1][3] - ONE;
16444 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16445 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16446 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16448 /* 2-square pawn move just happened */
16450 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16454 } else if(move == backwardMostMove) {
16455 // [HGM] perhaps we should always do it like this, and forget the above?
16456 if((signed char)boards[move][EP_STATUS] >= 0) {
16457 *p++ = boards[move][EP_STATUS] + AAA;
16458 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16469 /* [HGM] find reversible plies */
16470 { int i = 0, j=move;
16472 if (appData.debugMode) { int k;
16473 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16474 for(k=backwardMostMove; k<=forwardMostMove; k++)
16475 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16479 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16480 if( j == backwardMostMove ) i += initialRulePlies;
16481 sprintf(p, "%d ", i);
16482 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16484 /* Fullmove number */
16485 sprintf(p, "%d", (move / 2) + 1);
16487 return StrSave(buf);
16491 ParseFEN(board, blackPlaysFirst, fen)
16493 int *blackPlaysFirst;
16503 /* [HGM] by default clear Crazyhouse holdings, if present */
16504 if(gameInfo.holdingsWidth) {
16505 for(i=0; i<BOARD_HEIGHT; i++) {
16506 board[i][0] = EmptySquare; /* black holdings */
16507 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16508 board[i][1] = (ChessSquare) 0; /* black counts */
16509 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16513 /* Piece placement data */
16514 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16517 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16518 if (*p == '/') p++;
16519 emptycount = gameInfo.boardWidth - j;
16520 while (emptycount--)
16521 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16523 #if(BOARD_FILES >= 10)
16524 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16525 p++; emptycount=10;
16526 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16527 while (emptycount--)
16528 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16530 } else if (isdigit(*p)) {
16531 emptycount = *p++ - '0';
16532 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16533 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16534 while (emptycount--)
16535 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16536 } else if (*p == '+' || isalpha(*p)) {
16537 if (j >= gameInfo.boardWidth) return FALSE;
16539 piece = CharToPiece(*++p);
16540 if(piece == EmptySquare) return FALSE; /* unknown piece */
16541 piece = (ChessSquare) (PROMOTED piece ); p++;
16542 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16543 } else piece = CharToPiece(*p++);
16545 if(piece==EmptySquare) return FALSE; /* unknown piece */
16546 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16547 piece = (ChessSquare) (PROMOTED piece);
16548 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16551 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16557 while (*p == '/' || *p == ' ') p++;
16559 /* [HGM] look for Crazyhouse holdings here */
16560 while(*p==' ') p++;
16561 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16563 if(*p == '-' ) p++; /* empty holdings */ else {
16564 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16565 /* if we would allow FEN reading to set board size, we would */
16566 /* have to add holdings and shift the board read so far here */
16567 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16569 if((int) piece >= (int) BlackPawn ) {
16570 i = (int)piece - (int)BlackPawn;
16571 i = PieceToNumber((ChessSquare)i);
16572 if( i >= gameInfo.holdingsSize ) return FALSE;
16573 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16574 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16576 i = (int)piece - (int)WhitePawn;
16577 i = PieceToNumber((ChessSquare)i);
16578 if( i >= gameInfo.holdingsSize ) return FALSE;
16579 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16580 board[i][BOARD_WIDTH-2]++; /* black holdings */
16587 while(*p == ' ') p++;
16591 if(appData.colorNickNames) {
16592 if( c == appData.colorNickNames[0] ) c = 'w'; else
16593 if( c == appData.colorNickNames[1] ) c = 'b';
16597 *blackPlaysFirst = FALSE;
16600 *blackPlaysFirst = TRUE;
16606 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16607 /* return the extra info in global variiables */
16609 /* set defaults in case FEN is incomplete */
16610 board[EP_STATUS] = EP_UNKNOWN;
16611 for(i=0; i<nrCastlingRights; i++ ) {
16612 board[CASTLING][i] =
16613 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16614 } /* assume possible unless obviously impossible */
16615 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16616 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16617 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16618 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16619 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16620 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16621 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16622 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16625 while(*p==' ') p++;
16626 if(nrCastlingRights) {
16627 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16628 /* castling indicator present, so default becomes no castlings */
16629 for(i=0; i<nrCastlingRights; i++ ) {
16630 board[CASTLING][i] = NoRights;
16633 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16634 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16635 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16636 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16637 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16639 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16640 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16641 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16643 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16644 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16645 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16646 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16647 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16648 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16651 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16652 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16653 board[CASTLING][2] = whiteKingFile;
16656 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16657 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16658 board[CASTLING][2] = whiteKingFile;
16661 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16662 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16663 board[CASTLING][5] = blackKingFile;
16666 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16667 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16668 board[CASTLING][5] = blackKingFile;
16671 default: /* FRC castlings */
16672 if(c >= 'a') { /* black rights */
16673 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16674 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16675 if(i == BOARD_RGHT) break;
16676 board[CASTLING][5] = i;
16678 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16679 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16681 board[CASTLING][3] = c;
16683 board[CASTLING][4] = c;
16684 } else { /* white rights */
16685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16686 if(board[0][i] == WhiteKing) break;
16687 if(i == BOARD_RGHT) break;
16688 board[CASTLING][2] = i;
16689 c -= AAA - 'a' + 'A';
16690 if(board[0][c] >= WhiteKing) break;
16692 board[CASTLING][0] = c;
16694 board[CASTLING][1] = c;
16698 for(i=0; i<nrCastlingRights; i++)
16699 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16700 if (appData.debugMode) {
16701 fprintf(debugFP, "FEN castling rights:");
16702 for(i=0; i<nrCastlingRights; i++)
16703 fprintf(debugFP, " %d", board[CASTLING][i]);
16704 fprintf(debugFP, "\n");
16707 while(*p==' ') p++;
16710 /* read e.p. field in games that know e.p. capture */
16711 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16712 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16714 p++; board[EP_STATUS] = EP_NONE;
16716 char c = *p++ - AAA;
16718 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16719 if(*p >= '0' && *p <='9') p++;
16720 board[EP_STATUS] = c;
16725 if(sscanf(p, "%d", &i) == 1) {
16726 FENrulePlies = i; /* 50-move ply counter */
16727 /* (The move number is still ignored) */
16734 EditPositionPasteFEN(char *fen)
16737 Board initial_position;
16739 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16740 DisplayError(_("Bad FEN position in clipboard"), 0);
16743 int savedBlackPlaysFirst = blackPlaysFirst;
16744 EditPositionEvent();
16745 blackPlaysFirst = savedBlackPlaysFirst;
16746 CopyBoard(boards[0], initial_position);
16747 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16748 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16749 DisplayBothClocks();
16750 DrawPosition(FALSE, boards[currentMove]);
16755 static char cseq[12] = "\\ ";
16757 Boolean set_cont_sequence(char *new_seq)
16762 // handle bad attempts to set the sequence
16764 return 0; // acceptable error - no debug
16766 len = strlen(new_seq);
16767 ret = (len > 0) && (len < sizeof(cseq));
16769 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16770 else if (appData.debugMode)
16771 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16776 reformat a source message so words don't cross the width boundary. internal
16777 newlines are not removed. returns the wrapped size (no null character unless
16778 included in source message). If dest is NULL, only calculate the size required
16779 for the dest buffer. lp argument indicats line position upon entry, and it's
16780 passed back upon exit.
16782 int wrap(char *dest, char *src, int count, int width, int *lp)
16784 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16786 cseq_len = strlen(cseq);
16787 old_line = line = *lp;
16788 ansi = len = clen = 0;
16790 for (i=0; i < count; i++)
16792 if (src[i] == '\033')
16795 // if we hit the width, back up
16796 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16798 // store i & len in case the word is too long
16799 old_i = i, old_len = len;
16801 // find the end of the last word
16802 while (i && src[i] != ' ' && src[i] != '\n')
16808 // word too long? restore i & len before splitting it
16809 if ((old_i-i+clen) >= width)
16816 if (i && src[i-1] == ' ')
16819 if (src[i] != ' ' && src[i] != '\n')
16826 // now append the newline and continuation sequence
16831 strncpy(dest+len, cseq, cseq_len);
16839 dest[len] = src[i];
16843 if (src[i] == '\n')
16848 if (dest && appData.debugMode)
16850 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16851 count, width, line, len, *lp);
16852 show_bytes(debugFP, src, count);
16853 fprintf(debugFP, "\ndest: ");
16854 show_bytes(debugFP, dest, len);
16855 fprintf(debugFP, "\n");
16857 *lp = dest ? line : old_line;
16862 // [HGM] vari: routines for shelving variations
16863 Boolean modeRestore = FALSE;
16866 PushInner(int firstMove, int lastMove)
16868 int i, j, nrMoves = lastMove - firstMove;
16870 // push current tail of game on stack
16871 savedResult[storedGames] = gameInfo.result;
16872 savedDetails[storedGames] = gameInfo.resultDetails;
16873 gameInfo.resultDetails = NULL;
16874 savedFirst[storedGames] = firstMove;
16875 savedLast [storedGames] = lastMove;
16876 savedFramePtr[storedGames] = framePtr;
16877 framePtr -= nrMoves; // reserve space for the boards
16878 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16879 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16880 for(j=0; j<MOVE_LEN; j++)
16881 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16882 for(j=0; j<2*MOVE_LEN; j++)
16883 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16884 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16885 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16886 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16887 pvInfoList[firstMove+i-1].depth = 0;
16888 commentList[framePtr+i] = commentList[firstMove+i];
16889 commentList[firstMove+i] = NULL;
16893 forwardMostMove = firstMove; // truncate game so we can start variation
16897 PushTail(int firstMove, int lastMove)
16899 if(appData.icsActive) { // only in local mode
16900 forwardMostMove = currentMove; // mimic old ICS behavior
16903 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16905 PushInner(firstMove, lastMove);
16906 if(storedGames == 1) GreyRevert(FALSE);
16907 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16911 PopInner(Boolean annotate)
16914 char buf[8000], moveBuf[20];
16916 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16917 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16918 nrMoves = savedLast[storedGames] - currentMove;
16921 if(!WhiteOnMove(currentMove))
16922 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16923 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16924 for(i=currentMove; i<forwardMostMove; i++) {
16926 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16927 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16928 strcat(buf, moveBuf);
16929 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16930 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16934 for(i=1; i<=nrMoves; i++) { // copy last variation back
16935 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16936 for(j=0; j<MOVE_LEN; j++)
16937 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16938 for(j=0; j<2*MOVE_LEN; j++)
16939 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16940 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16941 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16942 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16943 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16944 commentList[currentMove+i] = commentList[framePtr+i];
16945 commentList[framePtr+i] = NULL;
16947 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16948 framePtr = savedFramePtr[storedGames];
16949 gameInfo.result = savedResult[storedGames];
16950 if(gameInfo.resultDetails != NULL) {
16951 free(gameInfo.resultDetails);
16953 gameInfo.resultDetails = savedDetails[storedGames];
16954 forwardMostMove = currentMove + nrMoves;
16958 PopTail(Boolean annotate)
16960 if(appData.icsActive) return FALSE; // only in local mode
16961 if(!storedGames) return FALSE; // sanity
16962 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16964 PopInner(annotate);
16965 if(currentMove < forwardMostMove) ForwardEvent(); else
16966 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16968 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16974 { // remove all shelved variations
16976 for(i=0; i<storedGames; i++) {
16977 if(savedDetails[i])
16978 free(savedDetails[i]);
16979 savedDetails[i] = NULL;
16981 for(i=framePtr; i<MAX_MOVES; i++) {
16982 if(commentList[i]) free(commentList[i]);
16983 commentList[i] = NULL;
16985 framePtr = MAX_MOVES-1;
16990 LoadVariation(int index, char *text)
16991 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16992 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16993 int level = 0, move;
16995 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16996 // first find outermost bracketing variation
16997 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16998 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16999 if(*p == '{') wait = '}'; else
17000 if(*p == '[') wait = ']'; else
17001 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17002 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17004 if(*p == wait) wait = NULLCHAR; // closing ]} found
17007 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17008 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17009 end[1] = NULLCHAR; // clip off comment beyond variation
17010 ToNrEvent(currentMove-1);
17011 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17012 // kludge: use ParsePV() to append variation to game
17013 move = currentMove;
17014 ParsePV(start, TRUE, TRUE);
17015 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17016 ClearPremoveHighlights();
17018 ToNrEvent(currentMove+1);