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;
11184 int counts[EmptySquare], soughtCounts[EmptySquare], reverseCounts[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11187 unsigned char piece, to;
11190 Move moveDatabase[4000000];
11193 void MakePieceList(Board board, int *counts)
11195 int r, f, n=Q_PROMO;
11196 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11197 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11198 int sq = f + (r<<4);
11199 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11200 quickBoard[sq] = ++n;
11202 pieceType[n] = board[r][f];
11203 counts[board[r][f]]++;
11204 if(board[r][f] == WhiteKing) pieceList[1] = sq; else
11205 if(board[r][f] == BlackKing) pieceList[2] = sq; // remember where Kings start, for castling
11210 void PackMove(int fromX, int fromY, int toX, int toY, char promoChar)
11212 int sq = fromX + (fromY<<4);
11213 int piece = quickBoard[sq];
11214 quickBoard[sq] = 0;
11215 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11219 moveDatabase[movePtr].piece = piece;
11220 quickBoard[sq] = piece;
11224 int PackGame(Board board)
11226 moveDatabase[movePtr++].piece = 0; // terminate previous game
11227 MakePieceList(board, counts);
11231 int QuickCompare(Board board, int *counts, int *maxCounts)
11232 { // compare according to search mode
11234 switch(appData.searchMode)
11236 case 1: // exact position match
11237 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11238 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11241 case 2: // can have extra material on empty squares
11242 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11243 if(board[r][f] == EmptySquare) continue;
11244 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11247 case 3: // material with exact Pawn structure
11248 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11249 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11250 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11251 } // fall through to material comparison
11252 case 4: // exact material
11253 for(r=0; r<EmptySquare; r++) if(counts[r] != soughtCounts[r]) return FALSE;
11255 case 5: // material range with given imbalance
11256 for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11257 case 6: // material range
11258 for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11263 int QuickScan(Board board, Move *move)
11264 { // reconstruct game,and compare all positions in it
11265 MakePieceList(board, counts);
11267 int piece = move->piece;
11268 int to = move->to, from = pieceList[piece];
11269 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11270 if(!piece) return FALSE;
11271 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11272 piece = (++move)->piece;
11273 from = pieceList[piece];
11274 counts[pieceType[piece]]--;
11275 pieceType[piece] = (ChessSquare) move->to;
11276 counts[move->to]++;
11277 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11278 counts[pieceType[quickBoard[to]]]--;
11279 quickBoard[to] = 0;
11282 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11283 from = pieceList[piece]; // first two elements of pieceList contain initial King positions
11284 piece = quickBoard[from]; // so this must be King
11285 quickBoard[from] = 0;
11286 quickBoard[to] = piece;
11287 pieceList[piece] = to;
11291 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11292 quickBoard[from] = 0;
11293 quickBoard[to] = piece;
11294 pieceList[piece] = to;
11295 if(QuickCompare(soughtBoard, soughtCounts, maxSought)) return TRUE;
11300 GameInfo dummyInfo;
11302 int GameContainsPosition(FILE *f, ListGame *lg)
11304 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11305 int fromX, fromY, toX, toY;
11307 static int initDone=FALSE;
11309 // weed out games based on numerical tag comparison
11310 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11311 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11312 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11313 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11315 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11318 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11319 else CopyBoard(boards[scratch], initialPosition); // default start position
11320 if(lg->moves && !QuickScan( boards[scratch], &moveDatabase[lg->moves] )) return -1; // quick scan rules out it is there
11322 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11323 fseek(f, lg->offset, 0);
11326 yyboardindex = scratch;
11327 quickFlag = plyNr+1;
11332 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11338 if(plyNr) return -1; // after we have seen moves, this is for new game
11341 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11342 case ImpossibleMove:
11343 case WhiteWins: // game ends here with these four
11346 case GameUnfinished:
11350 if(appData.testLegality) return -1;
11351 case WhiteCapturesEnPassant:
11352 case BlackCapturesEnPassant:
11353 case WhitePromotion:
11354 case BlackPromotion:
11355 case WhiteNonPromotion:
11356 case BlackNonPromotion:
11358 case WhiteKingSideCastle:
11359 case WhiteQueenSideCastle:
11360 case BlackKingSideCastle:
11361 case BlackQueenSideCastle:
11362 case WhiteKingSideCastleWild:
11363 case WhiteQueenSideCastleWild:
11364 case BlackKingSideCastleWild:
11365 case BlackQueenSideCastleWild:
11366 case WhiteHSideCastleFR:
11367 case WhiteASideCastleFR:
11368 case BlackHSideCastleFR:
11369 case BlackASideCastleFR:
11370 fromX = currentMoveString[0] - AAA;
11371 fromY = currentMoveString[1] - ONE;
11372 toX = currentMoveString[2] - AAA;
11373 toY = currentMoveString[3] - ONE;
11374 promoChar = currentMoveString[4];
11378 fromX = next == WhiteDrop ?
11379 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11380 (int) CharToPiece(ToLower(currentMoveString[0]));
11382 toX = currentMoveString[2] - AAA;
11383 toY = currentMoveString[3] - ONE;
11386 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11388 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11389 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11393 /* Load the nth game from open file f */
11395 LoadGame(f, gameNumber, title, useList)
11403 int gn = gameNumber;
11404 ListGame *lg = NULL;
11405 int numPGNTags = 0;
11407 GameMode oldGameMode;
11408 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11410 if (appData.debugMode)
11411 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11413 if (gameMode == Training )
11414 SetTrainingModeOff();
11416 oldGameMode = gameMode;
11417 if (gameMode != BeginningOfGame) {
11418 Reset(FALSE, TRUE);
11422 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11423 fclose(lastLoadGameFP);
11427 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11430 fseek(f, lg->offset, 0);
11431 GameListHighlight(gameNumber);
11432 pos = lg->position;
11436 DisplayError(_("Game number out of range"), 0);
11441 if (fseek(f, 0, 0) == -1) {
11442 if (f == lastLoadGameFP ?
11443 gameNumber == lastLoadGameNumber + 1 :
11447 DisplayError(_("Can't seek on game file"), 0);
11452 lastLoadGameFP = f;
11453 lastLoadGameNumber = gameNumber;
11454 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11455 lastLoadGameUseList = useList;
11459 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11460 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11461 lg->gameInfo.black);
11463 } else if (*title != NULLCHAR) {
11464 if (gameNumber > 1) {
11465 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11468 DisplayTitle(title);
11472 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11473 gameMode = PlayFromGameFile;
11477 currentMove = forwardMostMove = backwardMostMove = 0;
11478 CopyBoard(boards[0], initialPosition);
11482 * Skip the first gn-1 games in the file.
11483 * Also skip over anything that precedes an identifiable
11484 * start of game marker, to avoid being confused by
11485 * garbage at the start of the file. Currently
11486 * recognized start of game markers are the move number "1",
11487 * the pattern "gnuchess .* game", the pattern
11488 * "^[#;%] [^ ]* game file", and a PGN tag block.
11489 * A game that starts with one of the latter two patterns
11490 * will also have a move number 1, possibly
11491 * following a position diagram.
11492 * 5-4-02: Let's try being more lenient and allowing a game to
11493 * start with an unnumbered move. Does that break anything?
11495 cm = lastLoadGameStart = EndOfFile;
11497 yyboardindex = forwardMostMove;
11498 cm = (ChessMove) Myylex();
11501 if (cmailMsgLoaded) {
11502 nCmailGames = CMAIL_MAX_GAMES - gn;
11505 DisplayError(_("Game not found in file"), 0);
11512 lastLoadGameStart = cm;
11515 case MoveNumberOne:
11516 switch (lastLoadGameStart) {
11521 case MoveNumberOne:
11523 gn--; /* count this game */
11524 lastLoadGameStart = cm;
11533 switch (lastLoadGameStart) {
11536 case MoveNumberOne:
11538 gn--; /* count this game */
11539 lastLoadGameStart = cm;
11542 lastLoadGameStart = cm; /* game counted already */
11550 yyboardindex = forwardMostMove;
11551 cm = (ChessMove) Myylex();
11552 } while (cm == PGNTag || cm == Comment);
11559 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11560 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11561 != CMAIL_OLD_RESULT) {
11563 cmailResult[ CMAIL_MAX_GAMES
11564 - gn - 1] = CMAIL_OLD_RESULT;
11570 /* Only a NormalMove can be at the start of a game
11571 * without a position diagram. */
11572 if (lastLoadGameStart == EndOfFile ) {
11574 lastLoadGameStart = MoveNumberOne;
11583 if (appData.debugMode)
11584 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11586 if (cm == XBoardGame) {
11587 /* Skip any header junk before position diagram and/or move 1 */
11589 yyboardindex = forwardMostMove;
11590 cm = (ChessMove) Myylex();
11592 if (cm == EndOfFile ||
11593 cm == GNUChessGame || cm == XBoardGame) {
11594 /* Empty game; pretend end-of-file and handle later */
11599 if (cm == MoveNumberOne || cm == PositionDiagram ||
11600 cm == PGNTag || cm == Comment)
11603 } else if (cm == GNUChessGame) {
11604 if (gameInfo.event != NULL) {
11605 free(gameInfo.event);
11607 gameInfo.event = StrSave(yy_text);
11610 startedFromSetupPosition = FALSE;
11611 while (cm == PGNTag) {
11612 if (appData.debugMode)
11613 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11614 err = ParsePGNTag(yy_text, &gameInfo);
11615 if (!err) numPGNTags++;
11617 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11618 if(gameInfo.variant != oldVariant) {
11619 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11620 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11621 InitPosition(TRUE);
11622 oldVariant = gameInfo.variant;
11623 if (appData.debugMode)
11624 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11628 if (gameInfo.fen != NULL) {
11629 Board initial_position;
11630 startedFromSetupPosition = TRUE;
11631 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11633 DisplayError(_("Bad FEN position in file"), 0);
11636 CopyBoard(boards[0], initial_position);
11637 if (blackPlaysFirst) {
11638 currentMove = forwardMostMove = backwardMostMove = 1;
11639 CopyBoard(boards[1], initial_position);
11640 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11641 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11642 timeRemaining[0][1] = whiteTimeRemaining;
11643 timeRemaining[1][1] = blackTimeRemaining;
11644 if (commentList[0] != NULL) {
11645 commentList[1] = commentList[0];
11646 commentList[0] = NULL;
11649 currentMove = forwardMostMove = backwardMostMove = 0;
11651 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11653 initialRulePlies = FENrulePlies;
11654 for( i=0; i< nrCastlingRights; i++ )
11655 initialRights[i] = initial_position[CASTLING][i];
11657 yyboardindex = forwardMostMove;
11658 free(gameInfo.fen);
11659 gameInfo.fen = NULL;
11662 yyboardindex = forwardMostMove;
11663 cm = (ChessMove) Myylex();
11665 /* Handle comments interspersed among the tags */
11666 while (cm == Comment) {
11668 if (appData.debugMode)
11669 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11671 AppendComment(currentMove, p, FALSE);
11672 yyboardindex = forwardMostMove;
11673 cm = (ChessMove) Myylex();
11677 /* don't rely on existence of Event tag since if game was
11678 * pasted from clipboard the Event tag may not exist
11680 if (numPGNTags > 0){
11682 if (gameInfo.variant == VariantNormal) {
11683 VariantClass v = StringToVariant(gameInfo.event);
11684 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11685 if(v < VariantShogi) gameInfo.variant = v;
11688 if( appData.autoDisplayTags ) {
11689 tags = PGNTags(&gameInfo);
11690 TagsPopUp(tags, CmailMsg());
11695 /* Make something up, but don't display it now */
11700 if (cm == PositionDiagram) {
11703 Board initial_position;
11705 if (appData.debugMode)
11706 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11708 if (!startedFromSetupPosition) {
11710 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11711 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11722 initial_position[i][j++] = CharToPiece(*p);
11725 while (*p == ' ' || *p == '\t' ||
11726 *p == '\n' || *p == '\r') p++;
11728 if (strncmp(p, "black", strlen("black"))==0)
11729 blackPlaysFirst = TRUE;
11731 blackPlaysFirst = FALSE;
11732 startedFromSetupPosition = TRUE;
11734 CopyBoard(boards[0], initial_position);
11735 if (blackPlaysFirst) {
11736 currentMove = forwardMostMove = backwardMostMove = 1;
11737 CopyBoard(boards[1], initial_position);
11738 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11739 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11740 timeRemaining[0][1] = whiteTimeRemaining;
11741 timeRemaining[1][1] = blackTimeRemaining;
11742 if (commentList[0] != NULL) {
11743 commentList[1] = commentList[0];
11744 commentList[0] = NULL;
11747 currentMove = forwardMostMove = backwardMostMove = 0;
11750 yyboardindex = forwardMostMove;
11751 cm = (ChessMove) Myylex();
11754 if (first.pr == NoProc) {
11755 StartChessProgram(&first);
11757 InitChessProgram(&first, FALSE);
11758 SendToProgram("force\n", &first);
11759 if (startedFromSetupPosition) {
11760 SendBoard(&first, forwardMostMove);
11761 if (appData.debugMode) {
11762 fprintf(debugFP, "Load Game\n");
11764 DisplayBothClocks();
11767 /* [HGM] server: flag to write setup moves in broadcast file as one */
11768 loadFlag = appData.suppressLoadMoves;
11770 while (cm == Comment) {
11772 if (appData.debugMode)
11773 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11775 AppendComment(currentMove, p, FALSE);
11776 yyboardindex = forwardMostMove;
11777 cm = (ChessMove) Myylex();
11780 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11781 cm == WhiteWins || cm == BlackWins ||
11782 cm == GameIsDrawn || cm == GameUnfinished) {
11783 DisplayMessage("", _("No moves in game"));
11784 if (cmailMsgLoaded) {
11785 if (appData.debugMode)
11786 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11790 DrawPosition(FALSE, boards[currentMove]);
11791 DisplayBothClocks();
11792 gameMode = EditGame;
11799 // [HGM] PV info: routine tests if comment empty
11800 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11801 DisplayComment(currentMove - 1, commentList[currentMove]);
11803 if (!matchMode && appData.timeDelay != 0)
11804 DrawPosition(FALSE, boards[currentMove]);
11806 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11807 programStats.ok_to_send = 1;
11810 /* if the first token after the PGN tags is a move
11811 * and not move number 1, retrieve it from the parser
11813 if (cm != MoveNumberOne)
11814 LoadGameOneMove(cm);
11816 /* load the remaining moves from the file */
11817 while (LoadGameOneMove(EndOfFile)) {
11818 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11819 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11822 /* rewind to the start of the game */
11823 currentMove = backwardMostMove;
11825 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11827 if (oldGameMode == AnalyzeFile ||
11828 oldGameMode == AnalyzeMode) {
11829 AnalyzeFileEvent();
11832 if (!matchMode && pos >= 0) {
11833 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11835 if (matchMode || appData.timeDelay == 0) {
11837 } else if (appData.timeDelay > 0) {
11838 AutoPlayGameLoop();
11841 if (appData.debugMode)
11842 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11844 loadFlag = 0; /* [HGM] true game starts */
11848 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11850 ReloadPosition(offset)
11853 int positionNumber = lastLoadPositionNumber + offset;
11854 if (lastLoadPositionFP == NULL) {
11855 DisplayError(_("No position has been loaded yet"), 0);
11858 if (positionNumber <= 0) {
11859 DisplayError(_("Can't back up any further"), 0);
11862 return LoadPosition(lastLoadPositionFP, positionNumber,
11863 lastLoadPositionTitle);
11866 /* Load the nth position from the given file */
11868 LoadPositionFromFile(filename, n, title)
11876 if (strcmp(filename, "-") == 0) {
11877 return LoadPosition(stdin, n, "stdin");
11879 f = fopen(filename, "rb");
11881 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11882 DisplayError(buf, errno);
11885 return LoadPosition(f, n, title);
11890 /* Load the nth position from the given open file, and close it */
11892 LoadPosition(f, positionNumber, title)
11894 int positionNumber;
11897 char *p, line[MSG_SIZ];
11898 Board initial_position;
11899 int i, j, fenMode, pn;
11901 if (gameMode == Training )
11902 SetTrainingModeOff();
11904 if (gameMode != BeginningOfGame) {
11905 Reset(FALSE, TRUE);
11907 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11908 fclose(lastLoadPositionFP);
11910 if (positionNumber == 0) positionNumber = 1;
11911 lastLoadPositionFP = f;
11912 lastLoadPositionNumber = positionNumber;
11913 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11914 if (first.pr == NoProc && !appData.noChessProgram) {
11915 StartChessProgram(&first);
11916 InitChessProgram(&first, FALSE);
11918 pn = positionNumber;
11919 if (positionNumber < 0) {
11920 /* Negative position number means to seek to that byte offset */
11921 if (fseek(f, -positionNumber, 0) == -1) {
11922 DisplayError(_("Can't seek on position file"), 0);
11927 if (fseek(f, 0, 0) == -1) {
11928 if (f == lastLoadPositionFP ?
11929 positionNumber == lastLoadPositionNumber + 1 :
11930 positionNumber == 1) {
11933 DisplayError(_("Can't seek on position file"), 0);
11938 /* See if this file is FEN or old-style xboard */
11939 if (fgets(line, MSG_SIZ, f) == NULL) {
11940 DisplayError(_("Position not found in file"), 0);
11943 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11944 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11947 if (fenMode || line[0] == '#') pn--;
11949 /* skip positions before number pn */
11950 if (fgets(line, MSG_SIZ, f) == NULL) {
11952 DisplayError(_("Position not found in file"), 0);
11955 if (fenMode || line[0] == '#') pn--;
11960 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11961 DisplayError(_("Bad FEN position in file"), 0);
11965 (void) fgets(line, MSG_SIZ, f);
11966 (void) fgets(line, MSG_SIZ, f);
11968 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11969 (void) fgets(line, MSG_SIZ, f);
11970 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11973 initial_position[i][j++] = CharToPiece(*p);
11977 blackPlaysFirst = FALSE;
11979 (void) fgets(line, MSG_SIZ, f);
11980 if (strncmp(line, "black", strlen("black"))==0)
11981 blackPlaysFirst = TRUE;
11984 startedFromSetupPosition = TRUE;
11986 CopyBoard(boards[0], initial_position);
11987 if (blackPlaysFirst) {
11988 currentMove = forwardMostMove = backwardMostMove = 1;
11989 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11990 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11991 CopyBoard(boards[1], initial_position);
11992 DisplayMessage("", _("Black to play"));
11994 currentMove = forwardMostMove = backwardMostMove = 0;
11995 DisplayMessage("", _("White to play"));
11997 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11998 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
11999 SendToProgram("force\n", &first);
12000 SendBoard(&first, forwardMostMove);
12002 if (appData.debugMode) {
12004 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12005 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12006 fprintf(debugFP, "Load Position\n");
12009 if (positionNumber > 1) {
12010 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12011 DisplayTitle(line);
12013 DisplayTitle(title);
12015 gameMode = EditGame;
12018 timeRemaining[0][1] = whiteTimeRemaining;
12019 timeRemaining[1][1] = blackTimeRemaining;
12020 DrawPosition(FALSE, boards[currentMove]);
12027 CopyPlayerNameIntoFileName(dest, src)
12030 while (*src != NULLCHAR && *src != ',') {
12035 *(*dest)++ = *src++;
12040 char *DefaultFileName(ext)
12043 static char def[MSG_SIZ];
12046 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12048 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12050 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12052 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12059 /* Save the current game to the given file */
12061 SaveGameToFile(filename, append)
12067 int result, i, t,tot=0;
12069 if (strcmp(filename, "-") == 0) {
12070 return SaveGame(stdout, 0, NULL);
12072 for(i=0; i<10; i++) { // upto 10 tries
12073 f = fopen(filename, append ? "a" : "w");
12074 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12075 if(f || errno != 13) break;
12076 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12080 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12081 DisplayError(buf, errno);
12084 safeStrCpy(buf, lastMsg, MSG_SIZ);
12085 DisplayMessage(_("Waiting for access to save file"), "");
12086 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12087 DisplayMessage(_("Saving game"), "");
12088 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
12089 result = SaveGame(f, 0, NULL);
12090 DisplayMessage(buf, "");
12100 static char buf[MSG_SIZ];
12103 p = strchr(str, ' ');
12104 if (p == NULL) return str;
12105 strncpy(buf, str, p - str);
12106 buf[p - str] = NULLCHAR;
12110 #define PGN_MAX_LINE 75
12112 #define PGN_SIDE_WHITE 0
12113 #define PGN_SIDE_BLACK 1
12116 static int FindFirstMoveOutOfBook( int side )
12120 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12121 int index = backwardMostMove;
12122 int has_book_hit = 0;
12124 if( (index % 2) != side ) {
12128 while( index < forwardMostMove ) {
12129 /* Check to see if engine is in book */
12130 int depth = pvInfoList[index].depth;
12131 int score = pvInfoList[index].score;
12137 else if( score == 0 && depth == 63 ) {
12138 in_book = 1; /* Zappa */
12140 else if( score == 2 && depth == 99 ) {
12141 in_book = 1; /* Abrok */
12144 has_book_hit += in_book;
12160 void GetOutOfBookInfo( char * buf )
12164 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12166 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12167 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12171 if( oob[0] >= 0 || oob[1] >= 0 ) {
12172 for( i=0; i<2; i++ ) {
12176 if( i > 0 && oob[0] >= 0 ) {
12177 strcat( buf, " " );
12180 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12181 sprintf( buf+strlen(buf), "%s%.2f",
12182 pvInfoList[idx].score >= 0 ? "+" : "",
12183 pvInfoList[idx].score / 100.0 );
12189 /* Save game in PGN style and close the file */
12194 int i, offset, linelen, newblock;
12198 int movelen, numlen, blank;
12199 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12201 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12203 tm = time((time_t *) NULL);
12205 PrintPGNTags(f, &gameInfo);
12207 if (backwardMostMove > 0 || startedFromSetupPosition) {
12208 char *fen = PositionToFEN(backwardMostMove, NULL);
12209 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12210 fprintf(f, "\n{--------------\n");
12211 PrintPosition(f, backwardMostMove);
12212 fprintf(f, "--------------}\n");
12216 /* [AS] Out of book annotation */
12217 if( appData.saveOutOfBookInfo ) {
12220 GetOutOfBookInfo( buf );
12222 if( buf[0] != '\0' ) {
12223 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12230 i = backwardMostMove;
12234 while (i < forwardMostMove) {
12235 /* Print comments preceding this move */
12236 if (commentList[i] != NULL) {
12237 if (linelen > 0) fprintf(f, "\n");
12238 fprintf(f, "%s", commentList[i]);
12243 /* Format move number */
12245 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12248 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12250 numtext[0] = NULLCHAR;
12252 numlen = strlen(numtext);
12255 /* Print move number */
12256 blank = linelen > 0 && numlen > 0;
12257 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12266 fprintf(f, "%s", numtext);
12270 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12271 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12274 blank = linelen > 0 && movelen > 0;
12275 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12284 fprintf(f, "%s", move_buffer);
12285 linelen += movelen;
12287 /* [AS] Add PV info if present */
12288 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12289 /* [HGM] add time */
12290 char buf[MSG_SIZ]; int seconds;
12292 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12298 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12301 seconds = (seconds + 4)/10; // round to full seconds
12303 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12305 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12308 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12309 pvInfoList[i].score >= 0 ? "+" : "",
12310 pvInfoList[i].score / 100.0,
12311 pvInfoList[i].depth,
12314 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12316 /* Print score/depth */
12317 blank = linelen > 0 && movelen > 0;
12318 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12327 fprintf(f, "%s", move_buffer);
12328 linelen += movelen;
12334 /* Start a new line */
12335 if (linelen > 0) fprintf(f, "\n");
12337 /* Print comments after last move */
12338 if (commentList[i] != NULL) {
12339 fprintf(f, "%s\n", commentList[i]);
12343 if (gameInfo.resultDetails != NULL &&
12344 gameInfo.resultDetails[0] != NULLCHAR) {
12345 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12346 PGNResult(gameInfo.result));
12348 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12352 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12356 /* Save game in old style and close the file */
12358 SaveGameOldStyle(f)
12364 tm = time((time_t *) NULL);
12366 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12369 if (backwardMostMove > 0 || startedFromSetupPosition) {
12370 fprintf(f, "\n[--------------\n");
12371 PrintPosition(f, backwardMostMove);
12372 fprintf(f, "--------------]\n");
12377 i = backwardMostMove;
12378 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12380 while (i < forwardMostMove) {
12381 if (commentList[i] != NULL) {
12382 fprintf(f, "[%s]\n", commentList[i]);
12385 if ((i % 2) == 1) {
12386 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12389 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12391 if (commentList[i] != NULL) {
12395 if (i >= forwardMostMove) {
12399 fprintf(f, "%s\n", parseList[i]);
12404 if (commentList[i] != NULL) {
12405 fprintf(f, "[%s]\n", commentList[i]);
12408 /* This isn't really the old style, but it's close enough */
12409 if (gameInfo.resultDetails != NULL &&
12410 gameInfo.resultDetails[0] != NULLCHAR) {
12411 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12412 gameInfo.resultDetails);
12414 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12421 /* Save the current game to open file f and close the file */
12423 SaveGame(f, dummy, dummy2)
12428 if (gameMode == EditPosition) EditPositionDone(TRUE);
12429 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12430 if (appData.oldSaveStyle)
12431 return SaveGameOldStyle(f);
12433 return SaveGamePGN(f);
12436 /* Save the current position to the given file */
12438 SavePositionToFile(filename)
12444 if (strcmp(filename, "-") == 0) {
12445 return SavePosition(stdout, 0, NULL);
12447 f = fopen(filename, "a");
12449 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12450 DisplayError(buf, errno);
12453 safeStrCpy(buf, lastMsg, MSG_SIZ);
12454 DisplayMessage(_("Waiting for access to save file"), "");
12455 flock(fileno(f), LOCK_EX); // [HGM] lock
12456 DisplayMessage(_("Saving position"), "");
12457 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12458 SavePosition(f, 0, NULL);
12459 DisplayMessage(buf, "");
12465 /* Save the current position to the given open file and close the file */
12467 SavePosition(f, dummy, dummy2)
12475 if (gameMode == EditPosition) EditPositionDone(TRUE);
12476 if (appData.oldSaveStyle) {
12477 tm = time((time_t *) NULL);
12479 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12481 fprintf(f, "[--------------\n");
12482 PrintPosition(f, currentMove);
12483 fprintf(f, "--------------]\n");
12485 fen = PositionToFEN(currentMove, NULL);
12486 fprintf(f, "%s\n", fen);
12494 ReloadCmailMsgEvent(unregister)
12498 static char *inFilename = NULL;
12499 static char *outFilename;
12501 struct stat inbuf, outbuf;
12504 /* Any registered moves are unregistered if unregister is set, */
12505 /* i.e. invoked by the signal handler */
12507 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12508 cmailMoveRegistered[i] = FALSE;
12509 if (cmailCommentList[i] != NULL) {
12510 free(cmailCommentList[i]);
12511 cmailCommentList[i] = NULL;
12514 nCmailMovesRegistered = 0;
12517 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12518 cmailResult[i] = CMAIL_NOT_RESULT;
12522 if (inFilename == NULL) {
12523 /* Because the filenames are static they only get malloced once */
12524 /* and they never get freed */
12525 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12526 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12528 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12529 sprintf(outFilename, "%s.out", appData.cmailGameName);
12532 status = stat(outFilename, &outbuf);
12534 cmailMailedMove = FALSE;
12536 status = stat(inFilename, &inbuf);
12537 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12540 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12541 counts the games, notes how each one terminated, etc.
12543 It would be nice to remove this kludge and instead gather all
12544 the information while building the game list. (And to keep it
12545 in the game list nodes instead of having a bunch of fixed-size
12546 parallel arrays.) Note this will require getting each game's
12547 termination from the PGN tags, as the game list builder does
12548 not process the game moves. --mann
12550 cmailMsgLoaded = TRUE;
12551 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12553 /* Load first game in the file or popup game menu */
12554 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12556 #endif /* !WIN32 */
12564 char string[MSG_SIZ];
12566 if ( cmailMailedMove
12567 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12568 return TRUE; /* Allow free viewing */
12571 /* Unregister move to ensure that we don't leave RegisterMove */
12572 /* with the move registered when the conditions for registering no */
12574 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12575 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12576 nCmailMovesRegistered --;
12578 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12580 free(cmailCommentList[lastLoadGameNumber - 1]);
12581 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12585 if (cmailOldMove == -1) {
12586 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12590 if (currentMove > cmailOldMove + 1) {
12591 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12595 if (currentMove < cmailOldMove) {
12596 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12600 if (forwardMostMove > currentMove) {
12601 /* Silently truncate extra moves */
12605 if ( (currentMove == cmailOldMove + 1)
12606 || ( (currentMove == cmailOldMove)
12607 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12608 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12609 if (gameInfo.result != GameUnfinished) {
12610 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12613 if (commentList[currentMove] != NULL) {
12614 cmailCommentList[lastLoadGameNumber - 1]
12615 = StrSave(commentList[currentMove]);
12617 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12619 if (appData.debugMode)
12620 fprintf(debugFP, "Saving %s for game %d\n",
12621 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12623 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12625 f = fopen(string, "w");
12626 if (appData.oldSaveStyle) {
12627 SaveGameOldStyle(f); /* also closes the file */
12629 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12630 f = fopen(string, "w");
12631 SavePosition(f, 0, NULL); /* also closes the file */
12633 fprintf(f, "{--------------\n");
12634 PrintPosition(f, currentMove);
12635 fprintf(f, "--------------}\n\n");
12637 SaveGame(f, 0, NULL); /* also closes the file*/
12640 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12641 nCmailMovesRegistered ++;
12642 } else if (nCmailGames == 1) {
12643 DisplayError(_("You have not made a move yet"), 0);
12654 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12655 FILE *commandOutput;
12656 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12657 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12663 if (! cmailMsgLoaded) {
12664 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12668 if (nCmailGames == nCmailResults) {
12669 DisplayError(_("No unfinished games"), 0);
12673 #if CMAIL_PROHIBIT_REMAIL
12674 if (cmailMailedMove) {
12675 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);
12676 DisplayError(msg, 0);
12681 if (! (cmailMailedMove || RegisterMove())) return;
12683 if ( cmailMailedMove
12684 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12685 snprintf(string, MSG_SIZ, partCommandString,
12686 appData.debugMode ? " -v" : "", appData.cmailGameName);
12687 commandOutput = popen(string, "r");
12689 if (commandOutput == NULL) {
12690 DisplayError(_("Failed to invoke cmail"), 0);
12692 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12693 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12695 if (nBuffers > 1) {
12696 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12697 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12698 nBytes = MSG_SIZ - 1;
12700 (void) memcpy(msg, buffer, nBytes);
12702 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12704 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12705 cmailMailedMove = TRUE; /* Prevent >1 moves */
12708 for (i = 0; i < nCmailGames; i ++) {
12709 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12714 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12716 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12718 appData.cmailGameName,
12720 LoadGameFromFile(buffer, 1, buffer, FALSE);
12721 cmailMsgLoaded = FALSE;
12725 DisplayInformation(msg);
12726 pclose(commandOutput);
12729 if ((*cmailMsg) != '\0') {
12730 DisplayInformation(cmailMsg);
12735 #endif /* !WIN32 */
12744 int prependComma = 0;
12746 char string[MSG_SIZ]; /* Space for game-list */
12749 if (!cmailMsgLoaded) return "";
12751 if (cmailMailedMove) {
12752 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12754 /* Create a list of games left */
12755 snprintf(string, MSG_SIZ, "[");
12756 for (i = 0; i < nCmailGames; i ++) {
12757 if (! ( cmailMoveRegistered[i]
12758 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12759 if (prependComma) {
12760 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12762 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12766 strcat(string, number);
12769 strcat(string, "]");
12771 if (nCmailMovesRegistered + nCmailResults == 0) {
12772 switch (nCmailGames) {
12774 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12778 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12782 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12787 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12789 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12794 if (nCmailResults == nCmailGames) {
12795 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12797 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12802 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12814 if (gameMode == Training)
12815 SetTrainingModeOff();
12818 cmailMsgLoaded = FALSE;
12819 if (appData.icsActive) {
12820 SendToICS(ics_prefix);
12821 SendToICS("refresh\n");
12831 /* Give up on clean exit */
12835 /* Keep trying for clean exit */
12839 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12841 if (telnetISR != NULL) {
12842 RemoveInputSource(telnetISR);
12844 if (icsPR != NoProc) {
12845 DestroyChildProcess(icsPR, TRUE);
12848 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12849 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12851 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12852 /* make sure this other one finishes before killing it! */
12853 if(endingGame) { int count = 0;
12854 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12855 while(endingGame && count++ < 10) DoSleep(1);
12856 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12859 /* Kill off chess programs */
12860 if (first.pr != NoProc) {
12863 DoSleep( appData.delayBeforeQuit );
12864 SendToProgram("quit\n", &first);
12865 DoSleep( appData.delayAfterQuit );
12866 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12868 if (second.pr != NoProc) {
12869 DoSleep( appData.delayBeforeQuit );
12870 SendToProgram("quit\n", &second);
12871 DoSleep( appData.delayAfterQuit );
12872 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12874 if (first.isr != NULL) {
12875 RemoveInputSource(first.isr);
12877 if (second.isr != NULL) {
12878 RemoveInputSource(second.isr);
12881 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12882 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12884 ShutDownFrontEnd();
12891 if (appData.debugMode)
12892 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12896 if (gameMode == MachinePlaysWhite ||
12897 gameMode == MachinePlaysBlack) {
12900 DisplayBothClocks();
12902 if (gameMode == PlayFromGameFile) {
12903 if (appData.timeDelay >= 0)
12904 AutoPlayGameLoop();
12905 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12906 Reset(FALSE, TRUE);
12907 SendToICS(ics_prefix);
12908 SendToICS("refresh\n");
12909 } else if (currentMove < forwardMostMove) {
12910 ForwardInner(forwardMostMove);
12912 pauseExamInvalid = FALSE;
12914 switch (gameMode) {
12918 pauseExamForwardMostMove = forwardMostMove;
12919 pauseExamInvalid = FALSE;
12922 case IcsPlayingWhite:
12923 case IcsPlayingBlack:
12927 case PlayFromGameFile:
12928 (void) StopLoadGameTimer();
12932 case BeginningOfGame:
12933 if (appData.icsActive) return;
12934 /* else fall through */
12935 case MachinePlaysWhite:
12936 case MachinePlaysBlack:
12937 case TwoMachinesPlay:
12938 if (forwardMostMove == 0)
12939 return; /* don't pause if no one has moved */
12940 if ((gameMode == MachinePlaysWhite &&
12941 !WhiteOnMove(forwardMostMove)) ||
12942 (gameMode == MachinePlaysBlack &&
12943 WhiteOnMove(forwardMostMove))) {
12956 char title[MSG_SIZ];
12958 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12959 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12961 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12962 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12963 parseList[currentMove - 1]);
12966 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12973 char *tags = PGNTags(&gameInfo);
12975 EditTagsPopUp(tags, NULL);
12982 if (appData.noChessProgram || gameMode == AnalyzeMode)
12985 if (gameMode != AnalyzeFile) {
12986 if (!appData.icsEngineAnalyze) {
12988 if (gameMode != EditGame) return;
12990 ResurrectChessProgram();
12991 SendToProgram("analyze\n", &first);
12992 first.analyzing = TRUE;
12993 /*first.maybeThinking = TRUE;*/
12994 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12995 EngineOutputPopUp();
12997 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13002 StartAnalysisClock();
13003 GetTimeMark(&lastNodeCountTime);
13010 if (appData.noChessProgram || gameMode == AnalyzeFile)
13013 if (gameMode != AnalyzeMode) {
13015 if (gameMode != EditGame) return;
13016 ResurrectChessProgram();
13017 SendToProgram("analyze\n", &first);
13018 first.analyzing = TRUE;
13019 /*first.maybeThinking = TRUE;*/
13020 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13021 EngineOutputPopUp();
13023 gameMode = AnalyzeFile;
13028 StartAnalysisClock();
13029 GetTimeMark(&lastNodeCountTime);
13031 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13035 MachineWhiteEvent()
13038 char *bookHit = NULL;
13040 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13044 if (gameMode == PlayFromGameFile ||
13045 gameMode == TwoMachinesPlay ||
13046 gameMode == Training ||
13047 gameMode == AnalyzeMode ||
13048 gameMode == EndOfGame)
13051 if (gameMode == EditPosition)
13052 EditPositionDone(TRUE);
13054 if (!WhiteOnMove(currentMove)) {
13055 DisplayError(_("It is not White's turn"), 0);
13059 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13062 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13063 gameMode == AnalyzeFile)
13066 ResurrectChessProgram(); /* in case it isn't running */
13067 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13068 gameMode = MachinePlaysWhite;
13071 gameMode = MachinePlaysWhite;
13075 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13077 if (first.sendName) {
13078 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13079 SendToProgram(buf, &first);
13081 if (first.sendTime) {
13082 if (first.useColors) {
13083 SendToProgram("black\n", &first); /*gnu kludge*/
13085 SendTimeRemaining(&first, TRUE);
13087 if (first.useColors) {
13088 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13090 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13091 SetMachineThinkingEnables();
13092 first.maybeThinking = TRUE;
13096 if (appData.autoFlipView && !flipView) {
13097 flipView = !flipView;
13098 DrawPosition(FALSE, NULL);
13099 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13102 if(bookHit) { // [HGM] book: simulate book reply
13103 static char bookMove[MSG_SIZ]; // a bit generous?
13105 programStats.nodes = programStats.depth = programStats.time =
13106 programStats.score = programStats.got_only_move = 0;
13107 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13109 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13110 strcat(bookMove, bookHit);
13111 HandleMachineMove(bookMove, &first);
13116 MachineBlackEvent()
13119 char *bookHit = NULL;
13121 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13125 if (gameMode == PlayFromGameFile ||
13126 gameMode == TwoMachinesPlay ||
13127 gameMode == Training ||
13128 gameMode == AnalyzeMode ||
13129 gameMode == EndOfGame)
13132 if (gameMode == EditPosition)
13133 EditPositionDone(TRUE);
13135 if (WhiteOnMove(currentMove)) {
13136 DisplayError(_("It is not Black's turn"), 0);
13140 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13143 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13144 gameMode == AnalyzeFile)
13147 ResurrectChessProgram(); /* in case it isn't running */
13148 gameMode = MachinePlaysBlack;
13152 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13154 if (first.sendName) {
13155 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13156 SendToProgram(buf, &first);
13158 if (first.sendTime) {
13159 if (first.useColors) {
13160 SendToProgram("white\n", &first); /*gnu kludge*/
13162 SendTimeRemaining(&first, FALSE);
13164 if (first.useColors) {
13165 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13167 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13168 SetMachineThinkingEnables();
13169 first.maybeThinking = TRUE;
13172 if (appData.autoFlipView && flipView) {
13173 flipView = !flipView;
13174 DrawPosition(FALSE, NULL);
13175 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13177 if(bookHit) { // [HGM] book: simulate book reply
13178 static char bookMove[MSG_SIZ]; // a bit generous?
13180 programStats.nodes = programStats.depth = programStats.time =
13181 programStats.score = programStats.got_only_move = 0;
13182 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13184 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13185 strcat(bookMove, bookHit);
13186 HandleMachineMove(bookMove, &first);
13192 DisplayTwoMachinesTitle()
13195 if (appData.matchGames > 0) {
13196 if(appData.tourneyFile[0]) {
13197 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13198 gameInfo.white, gameInfo.black,
13199 nextGame+1, appData.matchGames+1,
13200 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13202 if (first.twoMachinesColor[0] == 'w') {
13203 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13204 gameInfo.white, gameInfo.black,
13205 first.matchWins, second.matchWins,
13206 matchGame - 1 - (first.matchWins + second.matchWins));
13208 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13209 gameInfo.white, gameInfo.black,
13210 second.matchWins, first.matchWins,
13211 matchGame - 1 - (first.matchWins + second.matchWins));
13214 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13220 SettingsMenuIfReady()
13222 if (second.lastPing != second.lastPong) {
13223 DisplayMessage("", _("Waiting for second chess program"));
13224 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13228 DisplayMessage("", "");
13229 SettingsPopUp(&second);
13233 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13236 if (cps->pr == NoProc) {
13237 StartChessProgram(cps);
13238 if (cps->protocolVersion == 1) {
13241 /* kludge: allow timeout for initial "feature" command */
13243 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13244 DisplayMessage("", buf);
13245 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13253 TwoMachinesEvent P((void))
13257 ChessProgramState *onmove;
13258 char *bookHit = NULL;
13259 static int stalling = 0;
13263 if (appData.noChessProgram) return;
13265 switch (gameMode) {
13266 case TwoMachinesPlay:
13268 case MachinePlaysWhite:
13269 case MachinePlaysBlack:
13270 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13271 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13275 case BeginningOfGame:
13276 case PlayFromGameFile:
13279 if (gameMode != EditGame) return;
13282 EditPositionDone(TRUE);
13293 // forwardMostMove = currentMove;
13294 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13296 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13298 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13299 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13300 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13304 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13305 SendToProgram("force\n", &second);
13307 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13310 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13311 if(appData.matchPause>10000 || appData.matchPause<10)
13312 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13313 wait = SubtractTimeMarks(&now, &pauseStart);
13314 if(wait < appData.matchPause) {
13315 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13319 DisplayMessage("", "");
13320 if (startedFromSetupPosition) {
13321 SendBoard(&second, backwardMostMove);
13322 if (appData.debugMode) {
13323 fprintf(debugFP, "Two Machines\n");
13326 for (i = backwardMostMove; i < forwardMostMove; i++) {
13327 SendMoveToProgram(i, &second);
13330 gameMode = TwoMachinesPlay;
13332 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13334 DisplayTwoMachinesTitle();
13336 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13341 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13342 SendToProgram(first.computerString, &first);
13343 if (first.sendName) {
13344 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13345 SendToProgram(buf, &first);
13347 SendToProgram(second.computerString, &second);
13348 if (second.sendName) {
13349 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13350 SendToProgram(buf, &second);
13354 if (!first.sendTime || !second.sendTime) {
13355 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13356 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13358 if (onmove->sendTime) {
13359 if (onmove->useColors) {
13360 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13362 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13364 if (onmove->useColors) {
13365 SendToProgram(onmove->twoMachinesColor, onmove);
13367 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13368 // SendToProgram("go\n", onmove);
13369 onmove->maybeThinking = TRUE;
13370 SetMachineThinkingEnables();
13374 if(bookHit) { // [HGM] book: simulate book reply
13375 static char bookMove[MSG_SIZ]; // a bit generous?
13377 programStats.nodes = programStats.depth = programStats.time =
13378 programStats.score = programStats.got_only_move = 0;
13379 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13381 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13382 strcat(bookMove, bookHit);
13383 savedMessage = bookMove; // args for deferred call
13384 savedState = onmove;
13385 ScheduleDelayedEvent(DeferredBookMove, 1);
13392 if (gameMode == Training) {
13393 SetTrainingModeOff();
13394 gameMode = PlayFromGameFile;
13395 DisplayMessage("", _("Training mode off"));
13397 gameMode = Training;
13398 animateTraining = appData.animate;
13400 /* make sure we are not already at the end of the game */
13401 if (currentMove < forwardMostMove) {
13402 SetTrainingModeOn();
13403 DisplayMessage("", _("Training mode on"));
13405 gameMode = PlayFromGameFile;
13406 DisplayError(_("Already at end of game"), 0);
13415 if (!appData.icsActive) return;
13416 switch (gameMode) {
13417 case IcsPlayingWhite:
13418 case IcsPlayingBlack:
13421 case BeginningOfGame:
13429 EditPositionDone(TRUE);
13442 gameMode = IcsIdle;
13453 switch (gameMode) {
13455 SetTrainingModeOff();
13457 case MachinePlaysWhite:
13458 case MachinePlaysBlack:
13459 case BeginningOfGame:
13460 SendToProgram("force\n", &first);
13461 SetUserThinkingEnables();
13463 case PlayFromGameFile:
13464 (void) StopLoadGameTimer();
13465 if (gameFileFP != NULL) {
13470 EditPositionDone(TRUE);
13475 SendToProgram("force\n", &first);
13477 case TwoMachinesPlay:
13478 GameEnds(EndOfFile, NULL, GE_PLAYER);
13479 ResurrectChessProgram();
13480 SetUserThinkingEnables();
13483 ResurrectChessProgram();
13485 case IcsPlayingBlack:
13486 case IcsPlayingWhite:
13487 DisplayError(_("Warning: You are still playing a game"), 0);
13490 DisplayError(_("Warning: You are still observing a game"), 0);
13493 DisplayError(_("Warning: You are still examining a game"), 0);
13504 first.offeredDraw = second.offeredDraw = 0;
13506 if (gameMode == PlayFromGameFile) {
13507 whiteTimeRemaining = timeRemaining[0][currentMove];
13508 blackTimeRemaining = timeRemaining[1][currentMove];
13512 if (gameMode == MachinePlaysWhite ||
13513 gameMode == MachinePlaysBlack ||
13514 gameMode == TwoMachinesPlay ||
13515 gameMode == EndOfGame) {
13516 i = forwardMostMove;
13517 while (i > currentMove) {
13518 SendToProgram("undo\n", &first);
13521 if(!adjustedClock) {
13522 whiteTimeRemaining = timeRemaining[0][currentMove];
13523 blackTimeRemaining = timeRemaining[1][currentMove];
13524 DisplayBothClocks();
13526 if (whiteFlag || blackFlag) {
13527 whiteFlag = blackFlag = 0;
13532 gameMode = EditGame;
13539 EditPositionEvent()
13541 if (gameMode == EditPosition) {
13547 if (gameMode != EditGame) return;
13549 gameMode = EditPosition;
13552 if (currentMove > 0)
13553 CopyBoard(boards[0], boards[currentMove]);
13555 blackPlaysFirst = !WhiteOnMove(currentMove);
13557 currentMove = forwardMostMove = backwardMostMove = 0;
13558 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13565 /* [DM] icsEngineAnalyze - possible call from other functions */
13566 if (appData.icsEngineAnalyze) {
13567 appData.icsEngineAnalyze = FALSE;
13569 DisplayMessage("",_("Close ICS engine analyze..."));
13571 if (first.analysisSupport && first.analyzing) {
13572 SendToProgram("exit\n", &first);
13573 first.analyzing = FALSE;
13575 thinkOutput[0] = NULLCHAR;
13579 EditPositionDone(Boolean fakeRights)
13581 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13583 startedFromSetupPosition = TRUE;
13584 InitChessProgram(&first, FALSE);
13585 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13586 boards[0][EP_STATUS] = EP_NONE;
13587 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13588 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13589 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13590 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13591 } else boards[0][CASTLING][2] = NoRights;
13592 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13593 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13594 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13595 } else boards[0][CASTLING][5] = NoRights;
13597 SendToProgram("force\n", &first);
13598 if (blackPlaysFirst) {
13599 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13600 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13601 currentMove = forwardMostMove = backwardMostMove = 1;
13602 CopyBoard(boards[1], boards[0]);
13604 currentMove = forwardMostMove = backwardMostMove = 0;
13606 SendBoard(&first, forwardMostMove);
13607 if (appData.debugMode) {
13608 fprintf(debugFP, "EditPosDone\n");
13611 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13612 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13613 gameMode = EditGame;
13615 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13616 ClearHighlights(); /* [AS] */
13619 /* Pause for `ms' milliseconds */
13620 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13630 } while (SubtractTimeMarks(&m2, &m1) < ms);
13633 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13635 SendMultiLineToICS(buf)
13638 char temp[MSG_SIZ+1], *p;
13645 strncpy(temp, buf, len);
13650 if (*p == '\n' || *p == '\r')
13655 strcat(temp, "\n");
13657 SendToPlayer(temp, strlen(temp));
13661 SetWhiteToPlayEvent()
13663 if (gameMode == EditPosition) {
13664 blackPlaysFirst = FALSE;
13665 DisplayBothClocks(); /* works because currentMove is 0 */
13666 } else if (gameMode == IcsExamining) {
13667 SendToICS(ics_prefix);
13668 SendToICS("tomove white\n");
13673 SetBlackToPlayEvent()
13675 if (gameMode == EditPosition) {
13676 blackPlaysFirst = TRUE;
13677 currentMove = 1; /* kludge */
13678 DisplayBothClocks();
13680 } else if (gameMode == IcsExamining) {
13681 SendToICS(ics_prefix);
13682 SendToICS("tomove black\n");
13687 EditPositionMenuEvent(selection, x, y)
13688 ChessSquare selection;
13692 ChessSquare piece = boards[0][y][x];
13694 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13696 switch (selection) {
13698 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13699 SendToICS(ics_prefix);
13700 SendToICS("bsetup clear\n");
13701 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13702 SendToICS(ics_prefix);
13703 SendToICS("clearboard\n");
13705 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13706 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13707 for (y = 0; y < BOARD_HEIGHT; y++) {
13708 if (gameMode == IcsExamining) {
13709 if (boards[currentMove][y][x] != EmptySquare) {
13710 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13715 boards[0][y][x] = p;
13720 if (gameMode == EditPosition) {
13721 DrawPosition(FALSE, boards[0]);
13726 SetWhiteToPlayEvent();
13730 SetBlackToPlayEvent();
13734 if (gameMode == IcsExamining) {
13735 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13736 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13739 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13740 if(x == BOARD_LEFT-2) {
13741 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13742 boards[0][y][1] = 0;
13744 if(x == BOARD_RGHT+1) {
13745 if(y >= gameInfo.holdingsSize) break;
13746 boards[0][y][BOARD_WIDTH-2] = 0;
13749 boards[0][y][x] = EmptySquare;
13750 DrawPosition(FALSE, boards[0]);
13755 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13756 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13757 selection = (ChessSquare) (PROMOTED piece);
13758 } else if(piece == EmptySquare) selection = WhiteSilver;
13759 else selection = (ChessSquare)((int)piece - 1);
13763 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13764 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13765 selection = (ChessSquare) (DEMOTED piece);
13766 } else if(piece == EmptySquare) selection = BlackSilver;
13767 else selection = (ChessSquare)((int)piece + 1);
13772 if(gameInfo.variant == VariantShatranj ||
13773 gameInfo.variant == VariantXiangqi ||
13774 gameInfo.variant == VariantCourier ||
13775 gameInfo.variant == VariantMakruk )
13776 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13781 if(gameInfo.variant == VariantXiangqi)
13782 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13783 if(gameInfo.variant == VariantKnightmate)
13784 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13787 if (gameMode == IcsExamining) {
13788 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13789 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13790 PieceToChar(selection), AAA + x, ONE + y);
13793 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13795 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13796 n = PieceToNumber(selection - BlackPawn);
13797 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13798 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13799 boards[0][BOARD_HEIGHT-1-n][1]++;
13801 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13802 n = PieceToNumber(selection);
13803 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13804 boards[0][n][BOARD_WIDTH-1] = selection;
13805 boards[0][n][BOARD_WIDTH-2]++;
13808 boards[0][y][x] = selection;
13809 DrawPosition(TRUE, boards[0]);
13817 DropMenuEvent(selection, x, y)
13818 ChessSquare selection;
13821 ChessMove moveType;
13823 switch (gameMode) {
13824 case IcsPlayingWhite:
13825 case MachinePlaysBlack:
13826 if (!WhiteOnMove(currentMove)) {
13827 DisplayMoveError(_("It is Black's turn"));
13830 moveType = WhiteDrop;
13832 case IcsPlayingBlack:
13833 case MachinePlaysWhite:
13834 if (WhiteOnMove(currentMove)) {
13835 DisplayMoveError(_("It is White's turn"));
13838 moveType = BlackDrop;
13841 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13847 if (moveType == BlackDrop && selection < BlackPawn) {
13848 selection = (ChessSquare) ((int) selection
13849 + (int) BlackPawn - (int) WhitePawn);
13851 if (boards[currentMove][y][x] != EmptySquare) {
13852 DisplayMoveError(_("That square is occupied"));
13856 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13862 /* Accept a pending offer of any kind from opponent */
13864 if (appData.icsActive) {
13865 SendToICS(ics_prefix);
13866 SendToICS("accept\n");
13867 } else if (cmailMsgLoaded) {
13868 if (currentMove == cmailOldMove &&
13869 commentList[cmailOldMove] != NULL &&
13870 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13871 "Black offers a draw" : "White offers a draw")) {
13873 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13874 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13876 DisplayError(_("There is no pending offer on this move"), 0);
13877 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13880 /* Not used for offers from chess program */
13887 /* Decline a pending offer of any kind from opponent */
13889 if (appData.icsActive) {
13890 SendToICS(ics_prefix);
13891 SendToICS("decline\n");
13892 } else if (cmailMsgLoaded) {
13893 if (currentMove == cmailOldMove &&
13894 commentList[cmailOldMove] != NULL &&
13895 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13896 "Black offers a draw" : "White offers a draw")) {
13898 AppendComment(cmailOldMove, "Draw declined", TRUE);
13899 DisplayComment(cmailOldMove - 1, "Draw declined");
13902 DisplayError(_("There is no pending offer on this move"), 0);
13905 /* Not used for offers from chess program */
13912 /* Issue ICS rematch command */
13913 if (appData.icsActive) {
13914 SendToICS(ics_prefix);
13915 SendToICS("rematch\n");
13922 /* Call your opponent's flag (claim a win on time) */
13923 if (appData.icsActive) {
13924 SendToICS(ics_prefix);
13925 SendToICS("flag\n");
13927 switch (gameMode) {
13930 case MachinePlaysWhite:
13933 GameEnds(GameIsDrawn, "Both players ran out of time",
13936 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13938 DisplayError(_("Your opponent is not out of time"), 0);
13941 case MachinePlaysBlack:
13944 GameEnds(GameIsDrawn, "Both players ran out of time",
13947 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13949 DisplayError(_("Your opponent is not out of time"), 0);
13957 ClockClick(int which)
13958 { // [HGM] code moved to back-end from winboard.c
13959 if(which) { // black clock
13960 if (gameMode == EditPosition || gameMode == IcsExamining) {
13961 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13962 SetBlackToPlayEvent();
13963 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13964 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13965 } else if (shiftKey) {
13966 AdjustClock(which, -1);
13967 } else if (gameMode == IcsPlayingWhite ||
13968 gameMode == MachinePlaysBlack) {
13971 } else { // white clock
13972 if (gameMode == EditPosition || gameMode == IcsExamining) {
13973 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13974 SetWhiteToPlayEvent();
13975 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13976 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13977 } else if (shiftKey) {
13978 AdjustClock(which, -1);
13979 } else if (gameMode == IcsPlayingBlack ||
13980 gameMode == MachinePlaysWhite) {
13989 /* Offer draw or accept pending draw offer from opponent */
13991 if (appData.icsActive) {
13992 /* Note: tournament rules require draw offers to be
13993 made after you make your move but before you punch
13994 your clock. Currently ICS doesn't let you do that;
13995 instead, you immediately punch your clock after making
13996 a move, but you can offer a draw at any time. */
13998 SendToICS(ics_prefix);
13999 SendToICS("draw\n");
14000 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14001 } else if (cmailMsgLoaded) {
14002 if (currentMove == cmailOldMove &&
14003 commentList[cmailOldMove] != NULL &&
14004 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14005 "Black offers a draw" : "White offers a draw")) {
14006 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14007 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14008 } else if (currentMove == cmailOldMove + 1) {
14009 char *offer = WhiteOnMove(cmailOldMove) ?
14010 "White offers a draw" : "Black offers a draw";
14011 AppendComment(currentMove, offer, TRUE);
14012 DisplayComment(currentMove - 1, offer);
14013 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14015 DisplayError(_("You must make your move before offering a draw"), 0);
14016 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14018 } else if (first.offeredDraw) {
14019 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14021 if (first.sendDrawOffers) {
14022 SendToProgram("draw\n", &first);
14023 userOfferedDraw = TRUE;
14031 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14033 if (appData.icsActive) {
14034 SendToICS(ics_prefix);
14035 SendToICS("adjourn\n");
14037 /* Currently GNU Chess doesn't offer or accept Adjourns */
14045 /* Offer Abort or accept pending Abort offer from opponent */
14047 if (appData.icsActive) {
14048 SendToICS(ics_prefix);
14049 SendToICS("abort\n");
14051 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14058 /* Resign. You can do this even if it's not your turn. */
14060 if (appData.icsActive) {
14061 SendToICS(ics_prefix);
14062 SendToICS("resign\n");
14064 switch (gameMode) {
14065 case MachinePlaysWhite:
14066 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14068 case MachinePlaysBlack:
14069 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14072 if (cmailMsgLoaded) {
14074 if (WhiteOnMove(cmailOldMove)) {
14075 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14077 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14079 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14090 StopObservingEvent()
14092 /* Stop observing current games */
14093 SendToICS(ics_prefix);
14094 SendToICS("unobserve\n");
14098 StopExaminingEvent()
14100 /* Stop observing current game */
14101 SendToICS(ics_prefix);
14102 SendToICS("unexamine\n");
14106 ForwardInner(target)
14111 if (appData.debugMode)
14112 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14113 target, currentMove, forwardMostMove);
14115 if (gameMode == EditPosition)
14118 if (gameMode == PlayFromGameFile && !pausing)
14121 if (gameMode == IcsExamining && pausing)
14122 limit = pauseExamForwardMostMove;
14124 limit = forwardMostMove;
14126 if (target > limit) target = limit;
14128 if (target > 0 && moveList[target - 1][0]) {
14129 int fromX, fromY, toX, toY;
14130 toX = moveList[target - 1][2] - AAA;
14131 toY = moveList[target - 1][3] - ONE;
14132 if (moveList[target - 1][1] == '@') {
14133 if (appData.highlightLastMove) {
14134 SetHighlights(-1, -1, toX, toY);
14137 fromX = moveList[target - 1][0] - AAA;
14138 fromY = moveList[target - 1][1] - ONE;
14139 if (target == currentMove + 1) {
14140 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14142 if (appData.highlightLastMove) {
14143 SetHighlights(fromX, fromY, toX, toY);
14147 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14148 gameMode == Training || gameMode == PlayFromGameFile ||
14149 gameMode == AnalyzeFile) {
14150 while (currentMove < target) {
14151 SendMoveToProgram(currentMove++, &first);
14154 currentMove = target;
14157 if (gameMode == EditGame || gameMode == EndOfGame) {
14158 whiteTimeRemaining = timeRemaining[0][currentMove];
14159 blackTimeRemaining = timeRemaining[1][currentMove];
14161 DisplayBothClocks();
14162 DisplayMove(currentMove - 1);
14163 DrawPosition(FALSE, boards[currentMove]);
14164 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14165 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14166 DisplayComment(currentMove - 1, commentList[currentMove]);
14174 if (gameMode == IcsExamining && !pausing) {
14175 SendToICS(ics_prefix);
14176 SendToICS("forward\n");
14178 ForwardInner(currentMove + 1);
14185 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14186 /* to optimze, we temporarily turn off analysis mode while we feed
14187 * the remaining moves to the engine. Otherwise we get analysis output
14190 if (first.analysisSupport) {
14191 SendToProgram("exit\nforce\n", &first);
14192 first.analyzing = FALSE;
14196 if (gameMode == IcsExamining && !pausing) {
14197 SendToICS(ics_prefix);
14198 SendToICS("forward 999999\n");
14200 ForwardInner(forwardMostMove);
14203 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14204 /* we have fed all the moves, so reactivate analysis mode */
14205 SendToProgram("analyze\n", &first);
14206 first.analyzing = TRUE;
14207 /*first.maybeThinking = TRUE;*/
14208 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14213 BackwardInner(target)
14216 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14218 if (appData.debugMode)
14219 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14220 target, currentMove, forwardMostMove);
14222 if (gameMode == EditPosition) return;
14223 if (currentMove <= backwardMostMove) {
14225 DrawPosition(full_redraw, boards[currentMove]);
14228 if (gameMode == PlayFromGameFile && !pausing)
14231 if (moveList[target][0]) {
14232 int fromX, fromY, toX, toY;
14233 toX = moveList[target][2] - AAA;
14234 toY = moveList[target][3] - ONE;
14235 if (moveList[target][1] == '@') {
14236 if (appData.highlightLastMove) {
14237 SetHighlights(-1, -1, toX, toY);
14240 fromX = moveList[target][0] - AAA;
14241 fromY = moveList[target][1] - ONE;
14242 if (target == currentMove - 1) {
14243 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14245 if (appData.highlightLastMove) {
14246 SetHighlights(fromX, fromY, toX, toY);
14250 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14251 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14252 while (currentMove > target) {
14253 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14254 // null move cannot be undone. Reload program with move history before it.
14256 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14257 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14259 SendBoard(&first, i);
14260 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14263 SendToProgram("undo\n", &first);
14267 currentMove = target;
14270 if (gameMode == EditGame || gameMode == EndOfGame) {
14271 whiteTimeRemaining = timeRemaining[0][currentMove];
14272 blackTimeRemaining = timeRemaining[1][currentMove];
14274 DisplayBothClocks();
14275 DisplayMove(currentMove - 1);
14276 DrawPosition(full_redraw, boards[currentMove]);
14277 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14278 // [HGM] PV info: routine tests if comment empty
14279 DisplayComment(currentMove - 1, commentList[currentMove]);
14285 if (gameMode == IcsExamining && !pausing) {
14286 SendToICS(ics_prefix);
14287 SendToICS("backward\n");
14289 BackwardInner(currentMove - 1);
14296 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14297 /* to optimize, we temporarily turn off analysis mode while we undo
14298 * all the moves. Otherwise we get analysis output after each undo.
14300 if (first.analysisSupport) {
14301 SendToProgram("exit\nforce\n", &first);
14302 first.analyzing = FALSE;
14306 if (gameMode == IcsExamining && !pausing) {
14307 SendToICS(ics_prefix);
14308 SendToICS("backward 999999\n");
14310 BackwardInner(backwardMostMove);
14313 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14314 /* we have fed all the moves, so reactivate analysis mode */
14315 SendToProgram("analyze\n", &first);
14316 first.analyzing = TRUE;
14317 /*first.maybeThinking = TRUE;*/
14318 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14325 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14326 if (to >= forwardMostMove) to = forwardMostMove;
14327 if (to <= backwardMostMove) to = backwardMostMove;
14328 if (to < currentMove) {
14336 RevertEvent(Boolean annotate)
14338 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14341 if (gameMode != IcsExamining) {
14342 DisplayError(_("You are not examining a game"), 0);
14346 DisplayError(_("You can't revert while pausing"), 0);
14349 SendToICS(ics_prefix);
14350 SendToICS("revert\n");
14356 switch (gameMode) {
14357 case MachinePlaysWhite:
14358 case MachinePlaysBlack:
14359 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14360 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14363 if (forwardMostMove < 2) return;
14364 currentMove = forwardMostMove = forwardMostMove - 2;
14365 whiteTimeRemaining = timeRemaining[0][currentMove];
14366 blackTimeRemaining = timeRemaining[1][currentMove];
14367 DisplayBothClocks();
14368 DisplayMove(currentMove - 1);
14369 ClearHighlights();/*!! could figure this out*/
14370 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14371 SendToProgram("remove\n", &first);
14372 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14375 case BeginningOfGame:
14379 case IcsPlayingWhite:
14380 case IcsPlayingBlack:
14381 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14382 SendToICS(ics_prefix);
14383 SendToICS("takeback 2\n");
14385 SendToICS(ics_prefix);
14386 SendToICS("takeback 1\n");
14395 ChessProgramState *cps;
14397 switch (gameMode) {
14398 case MachinePlaysWhite:
14399 if (!WhiteOnMove(forwardMostMove)) {
14400 DisplayError(_("It is your turn"), 0);
14405 case MachinePlaysBlack:
14406 if (WhiteOnMove(forwardMostMove)) {
14407 DisplayError(_("It is your turn"), 0);
14412 case TwoMachinesPlay:
14413 if (WhiteOnMove(forwardMostMove) ==
14414 (first.twoMachinesColor[0] == 'w')) {
14420 case BeginningOfGame:
14424 SendToProgram("?\n", cps);
14428 TruncateGameEvent()
14431 if (gameMode != EditGame) return;
14438 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14439 if (forwardMostMove > currentMove) {
14440 if (gameInfo.resultDetails != NULL) {
14441 free(gameInfo.resultDetails);
14442 gameInfo.resultDetails = NULL;
14443 gameInfo.result = GameUnfinished;
14445 forwardMostMove = currentMove;
14446 HistorySet(parseList, backwardMostMove, forwardMostMove,
14454 if (appData.noChessProgram) return;
14455 switch (gameMode) {
14456 case MachinePlaysWhite:
14457 if (WhiteOnMove(forwardMostMove)) {
14458 DisplayError(_("Wait until your turn"), 0);
14462 case BeginningOfGame:
14463 case MachinePlaysBlack:
14464 if (!WhiteOnMove(forwardMostMove)) {
14465 DisplayError(_("Wait until your turn"), 0);
14470 DisplayError(_("No hint available"), 0);
14473 SendToProgram("hint\n", &first);
14474 hintRequested = TRUE;
14480 if (appData.noChessProgram) return;
14481 switch (gameMode) {
14482 case MachinePlaysWhite:
14483 if (WhiteOnMove(forwardMostMove)) {
14484 DisplayError(_("Wait until your turn"), 0);
14488 case BeginningOfGame:
14489 case MachinePlaysBlack:
14490 if (!WhiteOnMove(forwardMostMove)) {
14491 DisplayError(_("Wait until your turn"), 0);
14496 EditPositionDone(TRUE);
14498 case TwoMachinesPlay:
14503 SendToProgram("bk\n", &first);
14504 bookOutput[0] = NULLCHAR;
14505 bookRequested = TRUE;
14511 char *tags = PGNTags(&gameInfo);
14512 TagsPopUp(tags, CmailMsg());
14516 /* end button procedures */
14519 PrintPosition(fp, move)
14525 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14526 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14527 char c = PieceToChar(boards[move][i][j]);
14528 fputc(c == 'x' ? '.' : c, fp);
14529 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14532 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14533 fprintf(fp, "white to play\n");
14535 fprintf(fp, "black to play\n");
14542 if (gameInfo.white != NULL) {
14543 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14549 /* Find last component of program's own name, using some heuristics */
14551 TidyProgramName(prog, host, buf)
14552 char *prog, *host, buf[MSG_SIZ];
14555 int local = (strcmp(host, "localhost") == 0);
14556 while (!local && (p = strchr(prog, ';')) != NULL) {
14558 while (*p == ' ') p++;
14561 if (*prog == '"' || *prog == '\'') {
14562 q = strchr(prog + 1, *prog);
14564 q = strchr(prog, ' ');
14566 if (q == NULL) q = prog + strlen(prog);
14568 while (p >= prog && *p != '/' && *p != '\\') p--;
14570 if(p == prog && *p == '"') p++;
14571 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14572 memcpy(buf, p, q - p);
14573 buf[q - p] = NULLCHAR;
14581 TimeControlTagValue()
14584 if (!appData.clockMode) {
14585 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14586 } else if (movesPerSession > 0) {
14587 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14588 } else if (timeIncrement == 0) {
14589 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14591 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14593 return StrSave(buf);
14599 /* This routine is used only for certain modes */
14600 VariantClass v = gameInfo.variant;
14601 ChessMove r = GameUnfinished;
14604 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14605 r = gameInfo.result;
14606 p = gameInfo.resultDetails;
14607 gameInfo.resultDetails = NULL;
14609 ClearGameInfo(&gameInfo);
14610 gameInfo.variant = v;
14612 switch (gameMode) {
14613 case MachinePlaysWhite:
14614 gameInfo.event = StrSave( appData.pgnEventHeader );
14615 gameInfo.site = StrSave(HostName());
14616 gameInfo.date = PGNDate();
14617 gameInfo.round = StrSave("-");
14618 gameInfo.white = StrSave(first.tidy);
14619 gameInfo.black = StrSave(UserName());
14620 gameInfo.timeControl = TimeControlTagValue();
14623 case MachinePlaysBlack:
14624 gameInfo.event = StrSave( appData.pgnEventHeader );
14625 gameInfo.site = StrSave(HostName());
14626 gameInfo.date = PGNDate();
14627 gameInfo.round = StrSave("-");
14628 gameInfo.white = StrSave(UserName());
14629 gameInfo.black = StrSave(first.tidy);
14630 gameInfo.timeControl = TimeControlTagValue();
14633 case TwoMachinesPlay:
14634 gameInfo.event = StrSave( appData.pgnEventHeader );
14635 gameInfo.site = StrSave(HostName());
14636 gameInfo.date = PGNDate();
14639 snprintf(buf, MSG_SIZ, "%d", roundNr);
14640 gameInfo.round = StrSave(buf);
14642 gameInfo.round = StrSave("-");
14644 if (first.twoMachinesColor[0] == 'w') {
14645 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14646 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14648 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14649 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14651 gameInfo.timeControl = TimeControlTagValue();
14655 gameInfo.event = StrSave("Edited game");
14656 gameInfo.site = StrSave(HostName());
14657 gameInfo.date = PGNDate();
14658 gameInfo.round = StrSave("-");
14659 gameInfo.white = StrSave("-");
14660 gameInfo.black = StrSave("-");
14661 gameInfo.result = r;
14662 gameInfo.resultDetails = p;
14666 gameInfo.event = StrSave("Edited position");
14667 gameInfo.site = StrSave(HostName());
14668 gameInfo.date = PGNDate();
14669 gameInfo.round = StrSave("-");
14670 gameInfo.white = StrSave("-");
14671 gameInfo.black = StrSave("-");
14674 case IcsPlayingWhite:
14675 case IcsPlayingBlack:
14680 case PlayFromGameFile:
14681 gameInfo.event = StrSave("Game from non-PGN file");
14682 gameInfo.site = StrSave(HostName());
14683 gameInfo.date = PGNDate();
14684 gameInfo.round = StrSave("-");
14685 gameInfo.white = StrSave("?");
14686 gameInfo.black = StrSave("?");
14695 ReplaceComment(index, text)
14703 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14704 pvInfoList[index-1].depth == len &&
14705 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14706 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14707 while (*text == '\n') text++;
14708 len = strlen(text);
14709 while (len > 0 && text[len - 1] == '\n') len--;
14711 if (commentList[index] != NULL)
14712 free(commentList[index]);
14715 commentList[index] = NULL;
14718 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14719 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14720 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14721 commentList[index] = (char *) malloc(len + 2);
14722 strncpy(commentList[index], text, len);
14723 commentList[index][len] = '\n';
14724 commentList[index][len + 1] = NULLCHAR;
14726 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14728 commentList[index] = (char *) malloc(len + 7);
14729 safeStrCpy(commentList[index], "{\n", 3);
14730 safeStrCpy(commentList[index]+2, text, len+1);
14731 commentList[index][len+2] = NULLCHAR;
14732 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14733 strcat(commentList[index], "\n}\n");
14747 if (ch == '\r') continue;
14749 } while (ch != '\0');
14753 AppendComment(index, text, addBraces)
14756 Boolean addBraces; // [HGM] braces: tells if we should add {}
14761 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14762 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14765 while (*text == '\n') text++;
14766 len = strlen(text);
14767 while (len > 0 && text[len - 1] == '\n') len--;
14769 if (len == 0) return;
14771 if (commentList[index] != NULL) {
14772 Boolean addClosingBrace = addBraces;
14773 old = commentList[index];
14774 oldlen = strlen(old);
14775 while(commentList[index][oldlen-1] == '\n')
14776 commentList[index][--oldlen] = NULLCHAR;
14777 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14778 safeStrCpy(commentList[index], old, oldlen + len + 6);
14780 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14781 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14782 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14783 while (*text == '\n') { text++; len--; }
14784 commentList[index][--oldlen] = NULLCHAR;
14786 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14787 else strcat(commentList[index], "\n");
14788 strcat(commentList[index], text);
14789 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14790 else strcat(commentList[index], "\n");
14792 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14794 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14795 else commentList[index][0] = NULLCHAR;
14796 strcat(commentList[index], text);
14797 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14798 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14802 static char * FindStr( char * text, char * sub_text )
14804 char * result = strstr( text, sub_text );
14806 if( result != NULL ) {
14807 result += strlen( sub_text );
14813 /* [AS] Try to extract PV info from PGN comment */
14814 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14815 char *GetInfoFromComment( int index, char * text )
14817 char * sep = text, *p;
14819 if( text != NULL && index > 0 ) {
14822 int time = -1, sec = 0, deci;
14823 char * s_eval = FindStr( text, "[%eval " );
14824 char * s_emt = FindStr( text, "[%emt " );
14826 if( s_eval != NULL || s_emt != NULL ) {
14830 if( s_eval != NULL ) {
14831 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14835 if( delim != ']' ) {
14840 if( s_emt != NULL ) {
14845 /* We expect something like: [+|-]nnn.nn/dd */
14848 if(*text != '{') return text; // [HGM] braces: must be normal comment
14850 sep = strchr( text, '/' );
14851 if( sep == NULL || sep < (text+4) ) {
14856 if(p[1] == '(') { // comment starts with PV
14857 p = strchr(p, ')'); // locate end of PV
14858 if(p == NULL || sep < p+5) return text;
14859 // at this point we have something like "{(.*) +0.23/6 ..."
14860 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14861 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14862 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14864 time = -1; sec = -1; deci = -1;
14865 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14866 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14867 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14868 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14872 if( score_lo < 0 || score_lo >= 100 ) {
14876 if(sec >= 0) time = 600*time + 10*sec; else
14877 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14879 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14881 /* [HGM] PV time: now locate end of PV info */
14882 while( *++sep >= '0' && *sep <= '9'); // strip depth
14884 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14886 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14888 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14889 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14900 pvInfoList[index-1].depth = depth;
14901 pvInfoList[index-1].score = score;
14902 pvInfoList[index-1].time = 10*time; // centi-sec
14903 if(*sep == '}') *sep = 0; else *--sep = '{';
14904 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14910 SendToProgram(message, cps)
14912 ChessProgramState *cps;
14914 int count, outCount, error;
14917 if (cps->pr == NoProc) return;
14920 if (appData.debugMode) {
14923 fprintf(debugFP, "%ld >%-6s: %s",
14924 SubtractTimeMarks(&now, &programStartTime),
14925 cps->which, message);
14928 count = strlen(message);
14929 outCount = OutputToProcess(cps->pr, message, count, &error);
14930 if (outCount < count && !exiting
14931 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14932 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14933 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14934 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14935 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14936 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14937 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14938 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14940 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14941 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14942 gameInfo.result = res;
14944 gameInfo.resultDetails = StrSave(buf);
14946 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14947 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14952 ReceiveFromProgram(isr, closure, message, count, error)
14953 InputSourceRef isr;
14961 ChessProgramState *cps = (ChessProgramState *)closure;
14963 if (isr != cps->isr) return; /* Killed intentionally */
14966 RemoveInputSource(cps->isr);
14967 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14968 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14969 _(cps->which), cps->program);
14970 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14971 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14972 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14973 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14974 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14976 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14977 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14978 gameInfo.result = res;
14980 gameInfo.resultDetails = StrSave(buf);
14982 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14983 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14985 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14986 _(cps->which), cps->program);
14987 RemoveInputSource(cps->isr);
14989 /* [AS] Program is misbehaving badly... kill it */
14990 if( count == -2 ) {
14991 DestroyChildProcess( cps->pr, 9 );
14995 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15000 if ((end_str = strchr(message, '\r')) != NULL)
15001 *end_str = NULLCHAR;
15002 if ((end_str = strchr(message, '\n')) != NULL)
15003 *end_str = NULLCHAR;
15005 if (appData.debugMode) {
15006 TimeMark now; int print = 1;
15007 char *quote = ""; char c; int i;
15009 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15010 char start = message[0];
15011 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15012 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15013 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15014 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15015 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15016 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15017 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15018 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15019 sscanf(message, "hint: %c", &c)!=1 &&
15020 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15021 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15022 print = (appData.engineComments >= 2);
15024 message[0] = start; // restore original message
15028 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15029 SubtractTimeMarks(&now, &programStartTime), cps->which,
15035 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15036 if (appData.icsEngineAnalyze) {
15037 if (strstr(message, "whisper") != NULL ||
15038 strstr(message, "kibitz") != NULL ||
15039 strstr(message, "tellics") != NULL) return;
15042 HandleMachineMove(message, cps);
15047 SendTimeControl(cps, mps, tc, inc, sd, st)
15048 ChessProgramState *cps;
15049 int mps, inc, sd, st;
15055 if( timeControl_2 > 0 ) {
15056 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15057 tc = timeControl_2;
15060 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15061 inc /= cps->timeOdds;
15062 st /= cps->timeOdds;
15064 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15067 /* Set exact time per move, normally using st command */
15068 if (cps->stKludge) {
15069 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15071 if (seconds == 0) {
15072 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15074 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15077 snprintf(buf, MSG_SIZ, "st %d\n", st);
15080 /* Set conventional or incremental time control, using level command */
15081 if (seconds == 0) {
15082 /* Note old gnuchess bug -- minutes:seconds used to not work.
15083 Fixed in later versions, but still avoid :seconds
15084 when seconds is 0. */
15085 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15087 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15088 seconds, inc/1000.);
15091 SendToProgram(buf, cps);
15093 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15094 /* Orthogonally, limit search to given depth */
15096 if (cps->sdKludge) {
15097 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15099 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15101 SendToProgram(buf, cps);
15104 if(cps->nps >= 0) { /* [HGM] nps */
15105 if(cps->supportsNPS == FALSE)
15106 cps->nps = -1; // don't use if engine explicitly says not supported!
15108 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15109 SendToProgram(buf, cps);
15114 ChessProgramState *WhitePlayer()
15115 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15117 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15118 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15124 SendTimeRemaining(cps, machineWhite)
15125 ChessProgramState *cps;
15126 int /*boolean*/ machineWhite;
15128 char message[MSG_SIZ];
15131 /* Note: this routine must be called when the clocks are stopped
15132 or when they have *just* been set or switched; otherwise
15133 it will be off by the time since the current tick started.
15135 if (machineWhite) {
15136 time = whiteTimeRemaining / 10;
15137 otime = blackTimeRemaining / 10;
15139 time = blackTimeRemaining / 10;
15140 otime = whiteTimeRemaining / 10;
15142 /* [HGM] translate opponent's time by time-odds factor */
15143 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15144 if (appData.debugMode) {
15145 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15148 if (time <= 0) time = 1;
15149 if (otime <= 0) otime = 1;
15151 snprintf(message, MSG_SIZ, "time %ld\n", time);
15152 SendToProgram(message, cps);
15154 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15155 SendToProgram(message, cps);
15159 BoolFeature(p, name, loc, cps)
15163 ChessProgramState *cps;
15166 int len = strlen(name);
15169 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15171 sscanf(*p, "%d", &val);
15173 while (**p && **p != ' ')
15175 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15176 SendToProgram(buf, cps);
15183 IntFeature(p, name, loc, cps)
15187 ChessProgramState *cps;
15190 int len = strlen(name);
15191 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15193 sscanf(*p, "%d", loc);
15194 while (**p && **p != ' ') (*p)++;
15195 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15196 SendToProgram(buf, cps);
15203 StringFeature(p, name, loc, cps)
15207 ChessProgramState *cps;
15210 int len = strlen(name);
15211 if (strncmp((*p), name, len) == 0
15212 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15214 sscanf(*p, "%[^\"]", loc);
15215 while (**p && **p != '\"') (*p)++;
15216 if (**p == '\"') (*p)++;
15217 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15218 SendToProgram(buf, cps);
15225 ParseOption(Option *opt, ChessProgramState *cps)
15226 // [HGM] options: process the string that defines an engine option, and determine
15227 // name, type, default value, and allowed value range
15229 char *p, *q, buf[MSG_SIZ];
15230 int n, min = (-1)<<31, max = 1<<31, def;
15232 if(p = strstr(opt->name, " -spin ")) {
15233 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15234 if(max < min) max = min; // enforce consistency
15235 if(def < min) def = min;
15236 if(def > max) def = max;
15241 } else if((p = strstr(opt->name, " -slider "))) {
15242 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15243 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15244 if(max < min) max = min; // enforce consistency
15245 if(def < min) def = min;
15246 if(def > max) def = max;
15250 opt->type = Spin; // Slider;
15251 } else if((p = strstr(opt->name, " -string "))) {
15252 opt->textValue = p+9;
15253 opt->type = TextBox;
15254 } else if((p = strstr(opt->name, " -file "))) {
15255 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15256 opt->textValue = p+7;
15257 opt->type = FileName; // FileName;
15258 } else if((p = strstr(opt->name, " -path "))) {
15259 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15260 opt->textValue = p+7;
15261 opt->type = PathName; // PathName;
15262 } else if(p = strstr(opt->name, " -check ")) {
15263 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15264 opt->value = (def != 0);
15265 opt->type = CheckBox;
15266 } else if(p = strstr(opt->name, " -combo ")) {
15267 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15268 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15269 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15270 opt->value = n = 0;
15271 while(q = StrStr(q, " /// ")) {
15272 n++; *q = 0; // count choices, and null-terminate each of them
15274 if(*q == '*') { // remember default, which is marked with * prefix
15278 cps->comboList[cps->comboCnt++] = q;
15280 cps->comboList[cps->comboCnt++] = NULL;
15282 opt->type = ComboBox;
15283 } else if(p = strstr(opt->name, " -button")) {
15284 opt->type = Button;
15285 } else if(p = strstr(opt->name, " -save")) {
15286 opt->type = SaveButton;
15287 } else return FALSE;
15288 *p = 0; // terminate option name
15289 // now look if the command-line options define a setting for this engine option.
15290 if(cps->optionSettings && cps->optionSettings[0])
15291 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15292 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15293 snprintf(buf, MSG_SIZ, "option %s", p);
15294 if(p = strstr(buf, ",")) *p = 0;
15295 if(q = strchr(buf, '=')) switch(opt->type) {
15297 for(n=0; n<opt->max; n++)
15298 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15301 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15305 opt->value = atoi(q+1);
15310 SendToProgram(buf, cps);
15316 FeatureDone(cps, val)
15317 ChessProgramState* cps;
15320 DelayedEventCallback cb = GetDelayedEvent();
15321 if ((cb == InitBackEnd3 && cps == &first) ||
15322 (cb == SettingsMenuIfReady && cps == &second) ||
15323 (cb == LoadEngine) ||
15324 (cb == TwoMachinesEventIfReady)) {
15325 CancelDelayedEvent();
15326 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15328 cps->initDone = val;
15331 /* Parse feature command from engine */
15333 ParseFeatures(args, cps)
15335 ChessProgramState *cps;
15343 while (*p == ' ') p++;
15344 if (*p == NULLCHAR) return;
15346 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15347 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15348 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15349 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15350 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15351 if (BoolFeature(&p, "reuse", &val, cps)) {
15352 /* Engine can disable reuse, but can't enable it if user said no */
15353 if (!val) cps->reuse = FALSE;
15356 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15357 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15358 if (gameMode == TwoMachinesPlay) {
15359 DisplayTwoMachinesTitle();
15365 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15366 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15367 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15368 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15369 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15370 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15371 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15372 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15373 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15374 if (IntFeature(&p, "done", &val, cps)) {
15375 FeatureDone(cps, val);
15378 /* Added by Tord: */
15379 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15380 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15381 /* End of additions by Tord */
15383 /* [HGM] added features: */
15384 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15385 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15386 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15387 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15388 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15389 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15390 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15391 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15392 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15393 SendToProgram(buf, cps);
15396 if(cps->nrOptions >= MAX_OPTIONS) {
15398 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15399 DisplayError(buf, 0);
15403 /* End of additions by HGM */
15405 /* unknown feature: complain and skip */
15407 while (*q && *q != '=') q++;
15408 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15409 SendToProgram(buf, cps);
15415 while (*p && *p != '\"') p++;
15416 if (*p == '\"') p++;
15418 while (*p && *p != ' ') p++;
15426 PeriodicUpdatesEvent(newState)
15429 if (newState == appData.periodicUpdates)
15432 appData.periodicUpdates=newState;
15434 /* Display type changes, so update it now */
15435 // DisplayAnalysis();
15437 /* Get the ball rolling again... */
15439 AnalysisPeriodicEvent(1);
15440 StartAnalysisClock();
15445 PonderNextMoveEvent(newState)
15448 if (newState == appData.ponderNextMove) return;
15449 if (gameMode == EditPosition) EditPositionDone(TRUE);
15451 SendToProgram("hard\n", &first);
15452 if (gameMode == TwoMachinesPlay) {
15453 SendToProgram("hard\n", &second);
15456 SendToProgram("easy\n", &first);
15457 thinkOutput[0] = NULLCHAR;
15458 if (gameMode == TwoMachinesPlay) {
15459 SendToProgram("easy\n", &second);
15462 appData.ponderNextMove = newState;
15466 NewSettingEvent(option, feature, command, value)
15468 int option, value, *feature;
15472 if (gameMode == EditPosition) EditPositionDone(TRUE);
15473 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15474 if(feature == NULL || *feature) SendToProgram(buf, &first);
15475 if (gameMode == TwoMachinesPlay) {
15476 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15481 ShowThinkingEvent()
15482 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15484 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15485 int newState = appData.showThinking
15486 // [HGM] thinking: other features now need thinking output as well
15487 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15489 if (oldState == newState) return;
15490 oldState = newState;
15491 if (gameMode == EditPosition) EditPositionDone(TRUE);
15493 SendToProgram("post\n", &first);
15494 if (gameMode == TwoMachinesPlay) {
15495 SendToProgram("post\n", &second);
15498 SendToProgram("nopost\n", &first);
15499 thinkOutput[0] = NULLCHAR;
15500 if (gameMode == TwoMachinesPlay) {
15501 SendToProgram("nopost\n", &second);
15504 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15508 AskQuestionEvent(title, question, replyPrefix, which)
15509 char *title; char *question; char *replyPrefix; char *which;
15511 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15512 if (pr == NoProc) return;
15513 AskQuestion(title, question, replyPrefix, pr);
15517 TypeInEvent(char firstChar)
15519 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15520 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15521 gameMode == AnalyzeMode || gameMode == EditGame ||
15522 gameMode == EditPosition || gameMode == IcsExamining ||
15523 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15524 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15525 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15526 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15527 gameMode == Training) PopUpMoveDialog(firstChar);
15531 TypeInDoneEvent(char *move)
15534 int n, fromX, fromY, toX, toY;
15536 ChessMove moveType;
15539 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15540 EditPositionPasteFEN(move);
15543 // [HGM] movenum: allow move number to be typed in any mode
15544 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15549 if (gameMode != EditGame && currentMove != forwardMostMove &&
15550 gameMode != Training) {
15551 DisplayMoveError(_("Displayed move is not current"));
15553 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15554 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15555 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15556 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15557 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15558 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15560 DisplayMoveError(_("Could not parse move"));
15566 DisplayMove(moveNumber)
15569 char message[MSG_SIZ];
15571 char cpThinkOutput[MSG_SIZ];
15573 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15575 if (moveNumber == forwardMostMove - 1 ||
15576 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15578 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15580 if (strchr(cpThinkOutput, '\n')) {
15581 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15584 *cpThinkOutput = NULLCHAR;
15587 /* [AS] Hide thinking from human user */
15588 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15589 *cpThinkOutput = NULLCHAR;
15590 if( thinkOutput[0] != NULLCHAR ) {
15593 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15594 cpThinkOutput[i] = '.';
15596 cpThinkOutput[i] = NULLCHAR;
15597 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15601 if (moveNumber == forwardMostMove - 1 &&
15602 gameInfo.resultDetails != NULL) {
15603 if (gameInfo.resultDetails[0] == NULLCHAR) {
15604 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15606 snprintf(res, MSG_SIZ, " {%s} %s",
15607 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15613 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15614 DisplayMessage(res, cpThinkOutput);
15616 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15617 WhiteOnMove(moveNumber) ? " " : ".. ",
15618 parseList[moveNumber], res);
15619 DisplayMessage(message, cpThinkOutput);
15624 DisplayComment(moveNumber, text)
15628 char title[MSG_SIZ];
15630 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15631 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15633 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15634 WhiteOnMove(moveNumber) ? " " : ".. ",
15635 parseList[moveNumber]);
15637 if (text != NULL && (appData.autoDisplayComment || commentUp))
15638 CommentPopUp(title, text);
15641 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15642 * might be busy thinking or pondering. It can be omitted if your
15643 * gnuchess is configured to stop thinking immediately on any user
15644 * input. However, that gnuchess feature depends on the FIONREAD
15645 * ioctl, which does not work properly on some flavors of Unix.
15649 ChessProgramState *cps;
15652 if (!cps->useSigint) return;
15653 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15654 switch (gameMode) {
15655 case MachinePlaysWhite:
15656 case MachinePlaysBlack:
15657 case TwoMachinesPlay:
15658 case IcsPlayingWhite:
15659 case IcsPlayingBlack:
15662 /* Skip if we know it isn't thinking */
15663 if (!cps->maybeThinking) return;
15664 if (appData.debugMode)
15665 fprintf(debugFP, "Interrupting %s\n", cps->which);
15666 InterruptChildProcess(cps->pr);
15667 cps->maybeThinking = FALSE;
15672 #endif /*ATTENTION*/
15678 if (whiteTimeRemaining <= 0) {
15681 if (appData.icsActive) {
15682 if (appData.autoCallFlag &&
15683 gameMode == IcsPlayingBlack && !blackFlag) {
15684 SendToICS(ics_prefix);
15685 SendToICS("flag\n");
15689 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15691 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15692 if (appData.autoCallFlag) {
15693 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15700 if (blackTimeRemaining <= 0) {
15703 if (appData.icsActive) {
15704 if (appData.autoCallFlag &&
15705 gameMode == IcsPlayingWhite && !whiteFlag) {
15706 SendToICS(ics_prefix);
15707 SendToICS("flag\n");
15711 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15713 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15714 if (appData.autoCallFlag) {
15715 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15728 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15729 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15732 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15734 if ( !WhiteOnMove(forwardMostMove) ) {
15735 /* White made time control */
15736 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15737 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15738 /* [HGM] time odds: correct new time quota for time odds! */
15739 / WhitePlayer()->timeOdds;
15740 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15742 lastBlack -= blackTimeRemaining;
15743 /* Black made time control */
15744 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15745 / WhitePlayer()->other->timeOdds;
15746 lastWhite = whiteTimeRemaining;
15751 DisplayBothClocks()
15753 int wom = gameMode == EditPosition ?
15754 !blackPlaysFirst : WhiteOnMove(currentMove);
15755 DisplayWhiteClock(whiteTimeRemaining, wom);
15756 DisplayBlackClock(blackTimeRemaining, !wom);
15760 /* Timekeeping seems to be a portability nightmare. I think everyone
15761 has ftime(), but I'm really not sure, so I'm including some ifdefs
15762 to use other calls if you don't. Clocks will be less accurate if
15763 you have neither ftime nor gettimeofday.
15766 /* VS 2008 requires the #include outside of the function */
15767 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15768 #include <sys/timeb.h>
15771 /* Get the current time as a TimeMark */
15776 #if HAVE_GETTIMEOFDAY
15778 struct timeval timeVal;
15779 struct timezone timeZone;
15781 gettimeofday(&timeVal, &timeZone);
15782 tm->sec = (long) timeVal.tv_sec;
15783 tm->ms = (int) (timeVal.tv_usec / 1000L);
15785 #else /*!HAVE_GETTIMEOFDAY*/
15788 // include <sys/timeb.h> / moved to just above start of function
15789 struct timeb timeB;
15792 tm->sec = (long) timeB.time;
15793 tm->ms = (int) timeB.millitm;
15795 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15796 tm->sec = (long) time(NULL);
15802 /* Return the difference in milliseconds between two
15803 time marks. We assume the difference will fit in a long!
15806 SubtractTimeMarks(tm2, tm1)
15807 TimeMark *tm2, *tm1;
15809 return 1000L*(tm2->sec - tm1->sec) +
15810 (long) (tm2->ms - tm1->ms);
15815 * Code to manage the game clocks.
15817 * In tournament play, black starts the clock and then white makes a move.
15818 * We give the human user a slight advantage if he is playing white---the
15819 * clocks don't run until he makes his first move, so it takes zero time.
15820 * Also, we don't account for network lag, so we could get out of sync
15821 * with GNU Chess's clock -- but then, referees are always right.
15824 static TimeMark tickStartTM;
15825 static long intendedTickLength;
15828 NextTickLength(timeRemaining)
15829 long timeRemaining;
15831 long nominalTickLength, nextTickLength;
15833 if (timeRemaining > 0L && timeRemaining <= 10000L)
15834 nominalTickLength = 100L;
15836 nominalTickLength = 1000L;
15837 nextTickLength = timeRemaining % nominalTickLength;
15838 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15840 return nextTickLength;
15843 /* Adjust clock one minute up or down */
15845 AdjustClock(Boolean which, int dir)
15847 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15848 if(which) blackTimeRemaining += 60000*dir;
15849 else whiteTimeRemaining += 60000*dir;
15850 DisplayBothClocks();
15851 adjustedClock = TRUE;
15854 /* Stop clocks and reset to a fresh time control */
15858 (void) StopClockTimer();
15859 if (appData.icsActive) {
15860 whiteTimeRemaining = blackTimeRemaining = 0;
15861 } else if (searchTime) {
15862 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15863 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15864 } else { /* [HGM] correct new time quote for time odds */
15865 whiteTC = blackTC = fullTimeControlString;
15866 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15867 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15869 if (whiteFlag || blackFlag) {
15871 whiteFlag = blackFlag = FALSE;
15873 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15874 DisplayBothClocks();
15875 adjustedClock = FALSE;
15878 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15880 /* Decrement running clock by amount of time that has passed */
15884 long timeRemaining;
15885 long lastTickLength, fudge;
15888 if (!appData.clockMode) return;
15889 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15893 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15895 /* Fudge if we woke up a little too soon */
15896 fudge = intendedTickLength - lastTickLength;
15897 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15899 if (WhiteOnMove(forwardMostMove)) {
15900 if(whiteNPS >= 0) lastTickLength = 0;
15901 timeRemaining = whiteTimeRemaining -= lastTickLength;
15902 if(timeRemaining < 0 && !appData.icsActive) {
15903 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15904 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15905 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15906 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15909 DisplayWhiteClock(whiteTimeRemaining - fudge,
15910 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15912 if(blackNPS >= 0) lastTickLength = 0;
15913 timeRemaining = blackTimeRemaining -= lastTickLength;
15914 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15915 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15917 blackStartMove = forwardMostMove;
15918 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15921 DisplayBlackClock(blackTimeRemaining - fudge,
15922 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15924 if (CheckFlags()) return;
15927 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15928 StartClockTimer(intendedTickLength);
15930 /* if the time remaining has fallen below the alarm threshold, sound the
15931 * alarm. if the alarm has sounded and (due to a takeback or time control
15932 * with increment) the time remaining has increased to a level above the
15933 * threshold, reset the alarm so it can sound again.
15936 if (appData.icsActive && appData.icsAlarm) {
15938 /* make sure we are dealing with the user's clock */
15939 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15940 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15943 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15944 alarmSounded = FALSE;
15945 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15947 alarmSounded = TRUE;
15953 /* A player has just moved, so stop the previously running
15954 clock and (if in clock mode) start the other one.
15955 We redisplay both clocks in case we're in ICS mode, because
15956 ICS gives us an update to both clocks after every move.
15957 Note that this routine is called *after* forwardMostMove
15958 is updated, so the last fractional tick must be subtracted
15959 from the color that is *not* on move now.
15962 SwitchClocks(int newMoveNr)
15964 long lastTickLength;
15966 int flagged = FALSE;
15970 if (StopClockTimer() && appData.clockMode) {
15971 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15972 if (!WhiteOnMove(forwardMostMove)) {
15973 if(blackNPS >= 0) lastTickLength = 0;
15974 blackTimeRemaining -= lastTickLength;
15975 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15976 // if(pvInfoList[forwardMostMove].time == -1)
15977 pvInfoList[forwardMostMove].time = // use GUI time
15978 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15980 if(whiteNPS >= 0) lastTickLength = 0;
15981 whiteTimeRemaining -= lastTickLength;
15982 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15983 // if(pvInfoList[forwardMostMove].time == -1)
15984 pvInfoList[forwardMostMove].time =
15985 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15987 flagged = CheckFlags();
15989 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15990 CheckTimeControl();
15992 if (flagged || !appData.clockMode) return;
15994 switch (gameMode) {
15995 case MachinePlaysBlack:
15996 case MachinePlaysWhite:
15997 case BeginningOfGame:
15998 if (pausing) return;
16002 case PlayFromGameFile:
16010 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16011 if(WhiteOnMove(forwardMostMove))
16012 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16013 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16017 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16018 whiteTimeRemaining : blackTimeRemaining);
16019 StartClockTimer(intendedTickLength);
16023 /* Stop both clocks */
16027 long lastTickLength;
16030 if (!StopClockTimer()) return;
16031 if (!appData.clockMode) return;
16035 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16036 if (WhiteOnMove(forwardMostMove)) {
16037 if(whiteNPS >= 0) lastTickLength = 0;
16038 whiteTimeRemaining -= lastTickLength;
16039 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16041 if(blackNPS >= 0) lastTickLength = 0;
16042 blackTimeRemaining -= lastTickLength;
16043 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16048 /* Start clock of player on move. Time may have been reset, so
16049 if clock is already running, stop and restart it. */
16053 (void) StopClockTimer(); /* in case it was running already */
16054 DisplayBothClocks();
16055 if (CheckFlags()) return;
16057 if (!appData.clockMode) return;
16058 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16060 GetTimeMark(&tickStartTM);
16061 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16062 whiteTimeRemaining : blackTimeRemaining);
16064 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16065 whiteNPS = blackNPS = -1;
16066 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16067 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16068 whiteNPS = first.nps;
16069 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16070 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16071 blackNPS = first.nps;
16072 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16073 whiteNPS = second.nps;
16074 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16075 blackNPS = second.nps;
16076 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16078 StartClockTimer(intendedTickLength);
16085 long second, minute, hour, day;
16087 static char buf[32];
16089 if (ms > 0 && ms <= 9900) {
16090 /* convert milliseconds to tenths, rounding up */
16091 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16093 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16097 /* convert milliseconds to seconds, rounding up */
16098 /* use floating point to avoid strangeness of integer division
16099 with negative dividends on many machines */
16100 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16107 day = second / (60 * 60 * 24);
16108 second = second % (60 * 60 * 24);
16109 hour = second / (60 * 60);
16110 second = second % (60 * 60);
16111 minute = second / 60;
16112 second = second % 60;
16115 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16116 sign, day, hour, minute, second);
16118 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16120 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16127 * This is necessary because some C libraries aren't ANSI C compliant yet.
16130 StrStr(string, match)
16131 char *string, *match;
16135 length = strlen(match);
16137 for (i = strlen(string) - length; i >= 0; i--, string++)
16138 if (!strncmp(match, string, length))
16145 StrCaseStr(string, match)
16146 char *string, *match;
16150 length = strlen(match);
16152 for (i = strlen(string) - length; i >= 0; i--, string++) {
16153 for (j = 0; j < length; j++) {
16154 if (ToLower(match[j]) != ToLower(string[j]))
16157 if (j == length) return string;
16171 c1 = ToLower(*s1++);
16172 c2 = ToLower(*s2++);
16173 if (c1 > c2) return 1;
16174 if (c1 < c2) return -1;
16175 if (c1 == NULLCHAR) return 0;
16184 return isupper(c) ? tolower(c) : c;
16192 return islower(c) ? toupper(c) : c;
16194 #endif /* !_amigados */
16202 if ((ret = (char *) malloc(strlen(s) + 1)))
16204 safeStrCpy(ret, s, strlen(s)+1);
16210 StrSavePtr(s, savePtr)
16211 char *s, **savePtr;
16216 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16217 safeStrCpy(*savePtr, s, strlen(s)+1);
16229 clock = time((time_t *)NULL);
16230 tm = localtime(&clock);
16231 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16232 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16233 return StrSave(buf);
16238 PositionToFEN(move, overrideCastling)
16240 char *overrideCastling;
16242 int i, j, fromX, fromY, toX, toY;
16249 whiteToPlay = (gameMode == EditPosition) ?
16250 !blackPlaysFirst : (move % 2 == 0);
16253 /* Piece placement data */
16254 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16255 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16257 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16258 if (boards[move][i][j] == EmptySquare) {
16260 } else { ChessSquare piece = boards[move][i][j];
16261 if (emptycount > 0) {
16262 if(emptycount<10) /* [HGM] can be >= 10 */
16263 *p++ = '0' + emptycount;
16264 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16267 if(PieceToChar(piece) == '+') {
16268 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16270 piece = (ChessSquare)(DEMOTED piece);
16272 *p++ = PieceToChar(piece);
16274 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16275 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16280 if (emptycount > 0) {
16281 if(emptycount<10) /* [HGM] can be >= 10 */
16282 *p++ = '0' + emptycount;
16283 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16290 /* [HGM] print Crazyhouse or Shogi holdings */
16291 if( gameInfo.holdingsWidth ) {
16292 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16294 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16295 piece = boards[move][i][BOARD_WIDTH-1];
16296 if( piece != EmptySquare )
16297 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16298 *p++ = PieceToChar(piece);
16300 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16301 piece = boards[move][BOARD_HEIGHT-i-1][0];
16302 if( piece != EmptySquare )
16303 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16304 *p++ = PieceToChar(piece);
16307 if( q == p ) *p++ = '-';
16313 *p++ = whiteToPlay ? 'w' : 'b';
16316 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16317 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16319 if(nrCastlingRights) {
16321 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16322 /* [HGM] write directly from rights */
16323 if(boards[move][CASTLING][2] != NoRights &&
16324 boards[move][CASTLING][0] != NoRights )
16325 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16326 if(boards[move][CASTLING][2] != NoRights &&
16327 boards[move][CASTLING][1] != NoRights )
16328 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16329 if(boards[move][CASTLING][5] != NoRights &&
16330 boards[move][CASTLING][3] != NoRights )
16331 *p++ = boards[move][CASTLING][3] + AAA;
16332 if(boards[move][CASTLING][5] != NoRights &&
16333 boards[move][CASTLING][4] != NoRights )
16334 *p++ = boards[move][CASTLING][4] + AAA;
16337 /* [HGM] write true castling rights */
16338 if( nrCastlingRights == 6 ) {
16339 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16340 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16341 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16342 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16343 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16344 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16345 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16346 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16349 if (q == p) *p++ = '-'; /* No castling rights */
16353 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16354 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16355 /* En passant target square */
16356 if (move > backwardMostMove) {
16357 fromX = moveList[move - 1][0] - AAA;
16358 fromY = moveList[move - 1][1] - ONE;
16359 toX = moveList[move - 1][2] - AAA;
16360 toY = moveList[move - 1][3] - ONE;
16361 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16362 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16363 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16365 /* 2-square pawn move just happened */
16367 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16371 } else if(move == backwardMostMove) {
16372 // [HGM] perhaps we should always do it like this, and forget the above?
16373 if((signed char)boards[move][EP_STATUS] >= 0) {
16374 *p++ = boards[move][EP_STATUS] + AAA;
16375 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16386 /* [HGM] find reversible plies */
16387 { int i = 0, j=move;
16389 if (appData.debugMode) { int k;
16390 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16391 for(k=backwardMostMove; k<=forwardMostMove; k++)
16392 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16396 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16397 if( j == backwardMostMove ) i += initialRulePlies;
16398 sprintf(p, "%d ", i);
16399 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16401 /* Fullmove number */
16402 sprintf(p, "%d", (move / 2) + 1);
16404 return StrSave(buf);
16408 ParseFEN(board, blackPlaysFirst, fen)
16410 int *blackPlaysFirst;
16420 /* [HGM] by default clear Crazyhouse holdings, if present */
16421 if(gameInfo.holdingsWidth) {
16422 for(i=0; i<BOARD_HEIGHT; i++) {
16423 board[i][0] = EmptySquare; /* black holdings */
16424 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16425 board[i][1] = (ChessSquare) 0; /* black counts */
16426 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16430 /* Piece placement data */
16431 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16434 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16435 if (*p == '/') p++;
16436 emptycount = gameInfo.boardWidth - j;
16437 while (emptycount--)
16438 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16440 #if(BOARD_FILES >= 10)
16441 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16442 p++; emptycount=10;
16443 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16444 while (emptycount--)
16445 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16447 } else if (isdigit(*p)) {
16448 emptycount = *p++ - '0';
16449 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16450 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16451 while (emptycount--)
16452 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16453 } else if (*p == '+' || isalpha(*p)) {
16454 if (j >= gameInfo.boardWidth) return FALSE;
16456 piece = CharToPiece(*++p);
16457 if(piece == EmptySquare) return FALSE; /* unknown piece */
16458 piece = (ChessSquare) (PROMOTED piece ); p++;
16459 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16460 } else piece = CharToPiece(*p++);
16462 if(piece==EmptySquare) return FALSE; /* unknown piece */
16463 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16464 piece = (ChessSquare) (PROMOTED piece);
16465 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16468 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16474 while (*p == '/' || *p == ' ') p++;
16476 /* [HGM] look for Crazyhouse holdings here */
16477 while(*p==' ') p++;
16478 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16480 if(*p == '-' ) p++; /* empty holdings */ else {
16481 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16482 /* if we would allow FEN reading to set board size, we would */
16483 /* have to add holdings and shift the board read so far here */
16484 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16486 if((int) piece >= (int) BlackPawn ) {
16487 i = (int)piece - (int)BlackPawn;
16488 i = PieceToNumber((ChessSquare)i);
16489 if( i >= gameInfo.holdingsSize ) return FALSE;
16490 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16491 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16493 i = (int)piece - (int)WhitePawn;
16494 i = PieceToNumber((ChessSquare)i);
16495 if( i >= gameInfo.holdingsSize ) return FALSE;
16496 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16497 board[i][BOARD_WIDTH-2]++; /* black holdings */
16504 while(*p == ' ') p++;
16508 if(appData.colorNickNames) {
16509 if( c == appData.colorNickNames[0] ) c = 'w'; else
16510 if( c == appData.colorNickNames[1] ) c = 'b';
16514 *blackPlaysFirst = FALSE;
16517 *blackPlaysFirst = TRUE;
16523 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16524 /* return the extra info in global variiables */
16526 /* set defaults in case FEN is incomplete */
16527 board[EP_STATUS] = EP_UNKNOWN;
16528 for(i=0; i<nrCastlingRights; i++ ) {
16529 board[CASTLING][i] =
16530 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16531 } /* assume possible unless obviously impossible */
16532 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16533 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16534 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16535 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16536 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16537 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16538 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16539 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16542 while(*p==' ') p++;
16543 if(nrCastlingRights) {
16544 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16545 /* castling indicator present, so default becomes no castlings */
16546 for(i=0; i<nrCastlingRights; i++ ) {
16547 board[CASTLING][i] = NoRights;
16550 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16551 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16552 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16553 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16554 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16556 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16557 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16558 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16560 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16561 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16562 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16563 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16564 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16565 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16568 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16569 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16570 board[CASTLING][2] = whiteKingFile;
16573 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16574 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16575 board[CASTLING][2] = whiteKingFile;
16578 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16579 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16580 board[CASTLING][5] = blackKingFile;
16583 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16584 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16585 board[CASTLING][5] = blackKingFile;
16588 default: /* FRC castlings */
16589 if(c >= 'a') { /* black rights */
16590 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16591 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16592 if(i == BOARD_RGHT) break;
16593 board[CASTLING][5] = i;
16595 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16596 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16598 board[CASTLING][3] = c;
16600 board[CASTLING][4] = c;
16601 } else { /* white rights */
16602 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16603 if(board[0][i] == WhiteKing) break;
16604 if(i == BOARD_RGHT) break;
16605 board[CASTLING][2] = i;
16606 c -= AAA - 'a' + 'A';
16607 if(board[0][c] >= WhiteKing) break;
16609 board[CASTLING][0] = c;
16611 board[CASTLING][1] = c;
16615 for(i=0; i<nrCastlingRights; i++)
16616 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16617 if (appData.debugMode) {
16618 fprintf(debugFP, "FEN castling rights:");
16619 for(i=0; i<nrCastlingRights; i++)
16620 fprintf(debugFP, " %d", board[CASTLING][i]);
16621 fprintf(debugFP, "\n");
16624 while(*p==' ') p++;
16627 /* read e.p. field in games that know e.p. capture */
16628 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16629 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16631 p++; board[EP_STATUS] = EP_NONE;
16633 char c = *p++ - AAA;
16635 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16636 if(*p >= '0' && *p <='9') p++;
16637 board[EP_STATUS] = c;
16642 if(sscanf(p, "%d", &i) == 1) {
16643 FENrulePlies = i; /* 50-move ply counter */
16644 /* (The move number is still ignored) */
16651 EditPositionPasteFEN(char *fen)
16654 Board initial_position;
16656 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16657 DisplayError(_("Bad FEN position in clipboard"), 0);
16660 int savedBlackPlaysFirst = blackPlaysFirst;
16661 EditPositionEvent();
16662 blackPlaysFirst = savedBlackPlaysFirst;
16663 CopyBoard(boards[0], initial_position);
16664 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16665 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16666 DisplayBothClocks();
16667 DrawPosition(FALSE, boards[currentMove]);
16672 static char cseq[12] = "\\ ";
16674 Boolean set_cont_sequence(char *new_seq)
16679 // handle bad attempts to set the sequence
16681 return 0; // acceptable error - no debug
16683 len = strlen(new_seq);
16684 ret = (len > 0) && (len < sizeof(cseq));
16686 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16687 else if (appData.debugMode)
16688 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16693 reformat a source message so words don't cross the width boundary. internal
16694 newlines are not removed. returns the wrapped size (no null character unless
16695 included in source message). If dest is NULL, only calculate the size required
16696 for the dest buffer. lp argument indicats line position upon entry, and it's
16697 passed back upon exit.
16699 int wrap(char *dest, char *src, int count, int width, int *lp)
16701 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16703 cseq_len = strlen(cseq);
16704 old_line = line = *lp;
16705 ansi = len = clen = 0;
16707 for (i=0; i < count; i++)
16709 if (src[i] == '\033')
16712 // if we hit the width, back up
16713 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16715 // store i & len in case the word is too long
16716 old_i = i, old_len = len;
16718 // find the end of the last word
16719 while (i && src[i] != ' ' && src[i] != '\n')
16725 // word too long? restore i & len before splitting it
16726 if ((old_i-i+clen) >= width)
16733 if (i && src[i-1] == ' ')
16736 if (src[i] != ' ' && src[i] != '\n')
16743 // now append the newline and continuation sequence
16748 strncpy(dest+len, cseq, cseq_len);
16756 dest[len] = src[i];
16760 if (src[i] == '\n')
16765 if (dest && appData.debugMode)
16767 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16768 count, width, line, len, *lp);
16769 show_bytes(debugFP, src, count);
16770 fprintf(debugFP, "\ndest: ");
16771 show_bytes(debugFP, dest, len);
16772 fprintf(debugFP, "\n");
16774 *lp = dest ? line : old_line;
16779 // [HGM] vari: routines for shelving variations
16780 Boolean modeRestore = FALSE;
16783 PushInner(int firstMove, int lastMove)
16785 int i, j, nrMoves = lastMove - firstMove;
16787 // push current tail of game on stack
16788 savedResult[storedGames] = gameInfo.result;
16789 savedDetails[storedGames] = gameInfo.resultDetails;
16790 gameInfo.resultDetails = NULL;
16791 savedFirst[storedGames] = firstMove;
16792 savedLast [storedGames] = lastMove;
16793 savedFramePtr[storedGames] = framePtr;
16794 framePtr -= nrMoves; // reserve space for the boards
16795 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16796 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16797 for(j=0; j<MOVE_LEN; j++)
16798 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16799 for(j=0; j<2*MOVE_LEN; j++)
16800 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16801 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16802 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16803 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16804 pvInfoList[firstMove+i-1].depth = 0;
16805 commentList[framePtr+i] = commentList[firstMove+i];
16806 commentList[firstMove+i] = NULL;
16810 forwardMostMove = firstMove; // truncate game so we can start variation
16814 PushTail(int firstMove, int lastMove)
16816 if(appData.icsActive) { // only in local mode
16817 forwardMostMove = currentMove; // mimic old ICS behavior
16820 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16822 PushInner(firstMove, lastMove);
16823 if(storedGames == 1) GreyRevert(FALSE);
16824 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16828 PopInner(Boolean annotate)
16831 char buf[8000], moveBuf[20];
16833 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16834 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16835 nrMoves = savedLast[storedGames] - currentMove;
16838 if(!WhiteOnMove(currentMove))
16839 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16840 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16841 for(i=currentMove; i<forwardMostMove; i++) {
16843 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16844 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16845 strcat(buf, moveBuf);
16846 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16847 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16851 for(i=1; i<=nrMoves; i++) { // copy last variation back
16852 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16853 for(j=0; j<MOVE_LEN; j++)
16854 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16855 for(j=0; j<2*MOVE_LEN; j++)
16856 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16857 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16858 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16859 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16860 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16861 commentList[currentMove+i] = commentList[framePtr+i];
16862 commentList[framePtr+i] = NULL;
16864 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16865 framePtr = savedFramePtr[storedGames];
16866 gameInfo.result = savedResult[storedGames];
16867 if(gameInfo.resultDetails != NULL) {
16868 free(gameInfo.resultDetails);
16870 gameInfo.resultDetails = savedDetails[storedGames];
16871 forwardMostMove = currentMove + nrMoves;
16875 PopTail(Boolean annotate)
16877 if(appData.icsActive) return FALSE; // only in local mode
16878 if(!storedGames) return FALSE; // sanity
16879 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16881 PopInner(annotate);
16882 if(currentMove < forwardMostMove) ForwardEvent(); else
16883 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16885 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16891 { // remove all shelved variations
16893 for(i=0; i<storedGames; i++) {
16894 if(savedDetails[i])
16895 free(savedDetails[i]);
16896 savedDetails[i] = NULL;
16898 for(i=framePtr; i<MAX_MOVES; i++) {
16899 if(commentList[i]) free(commentList[i]);
16900 commentList[i] = NULL;
16902 framePtr = MAX_MOVES-1;
16907 LoadVariation(int index, char *text)
16908 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16909 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16910 int level = 0, move;
16912 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16913 // first find outermost bracketing variation
16914 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16915 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16916 if(*p == '{') wait = '}'; else
16917 if(*p == '[') wait = ']'; else
16918 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16919 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16921 if(*p == wait) wait = NULLCHAR; // closing ]} found
16924 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16925 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16926 end[1] = NULLCHAR; // clip off comment beyond variation
16927 ToNrEvent(currentMove-1);
16928 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16929 // kludge: use ParsePV() to append variation to game
16930 move = currentMove;
16931 ParsePV(start, TRUE, TRUE);
16932 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16933 ClearPremoveHighlights();
16935 ToNrEvent(currentMove+1);