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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
239 extern void ConsoleCreate();
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
262 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
285 /* States for ics_getting_history */
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
293 /* whosays values for GameEnds */
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
305 /* Different types of move when calling RegisterMove */
307 #define CMAIL_RESIGN 1
309 #define CMAIL_ACCEPT 3
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
316 /* Telnet protocol constants */
327 safeStrCpy( char *dst, const char *src, size_t count )
330 assert( dst != NULL );
331 assert( src != NULL );
334 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335 if( i == count && dst[count-1] != NULLCHAR)
337 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338 if(appData.debugMode)
339 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
345 /* Some compiler can't cast u64 to double
346 * This function do the job for us:
348 * We use the highest bit for cast, this only
349 * works if the highest bit is not
350 * in use (This should not happen)
352 * We used this for all compiler
355 u64ToDouble(u64 value)
358 u64 tmp = value & u64Const(0x7fffffffffffffff);
359 r = (double)(s64)tmp;
360 if (value & u64Const(0x8000000000000000))
361 r += 9.2233720368547758080e18; /* 2^63 */
365 /* Fake up flags for now, as we aren't keeping track of castling
366 availability yet. [HGM] Change of logic: the flag now only
367 indicates the type of castlings allowed by the rule of the game.
368 The actual rights themselves are maintained in the array
369 castlingRights, as part of the game history, and are not probed
375 int flags = F_ALL_CASTLE_OK;
376 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377 switch (gameInfo.variant) {
379 flags &= ~F_ALL_CASTLE_OK;
380 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381 flags |= F_IGNORE_CHECK;
383 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388 case VariantKriegspiel:
389 flags |= F_KRIEGSPIEL_CAPTURE;
391 case VariantCapaRandom:
392 case VariantFischeRandom:
393 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394 case VariantNoCastle:
395 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 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
477 /* animateTraining preserves the state of appData.animate
478 * when Training mode is activated. This allows the
479 * response to be animated when appData.animate == TRUE and
480 * appData.animateDragging == TRUE.
482 Boolean animateTraining;
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char initialRights[BOARD_FILES];
492 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int initialRulePlies, FENrulePlies;
494 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int mute; // mute all sounds
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
515 ChessSquare FIDEArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackBishop, BlackKnight, BlackRook }
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526 BlackKing, BlackKing, BlackKnight, BlackRook }
529 ChessSquare KnightmateArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532 { BlackRook, BlackMan, BlackBishop, BlackQueen,
533 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackMan, BlackFerz,
561 BlackKing, BlackMan, BlackKnight, BlackRook }
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating(str)
652 while(*str && !isdigit(*str)) ++str;
654 return 0; /* One of the special "no rating" cases */
662 /* Init programStats */
663 programStats.movelist[0] = 0;
664 programStats.depth = 0;
665 programStats.nr_moves = 0;
666 programStats.moves_left = 0;
667 programStats.nodes = 0;
668 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
669 programStats.score = 0;
670 programStats.got_only_move = 0;
671 programStats.got_fail = 0;
672 programStats.line_is_book = 0;
677 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678 if (appData.firstPlaysBlack) {
679 first.twoMachinesColor = "black\n";
680 second.twoMachinesColor = "white\n";
682 first.twoMachinesColor = "white\n";
683 second.twoMachinesColor = "black\n";
686 first.other = &second;
687 second.other = &first;
690 if(appData.timeOddsMode) {
691 norm = appData.timeOdds[0];
692 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694 first.timeOdds = appData.timeOdds[0]/norm;
695 second.timeOdds = appData.timeOdds[1]/norm;
698 if(programVersion) free(programVersion);
699 if (appData.noChessProgram) {
700 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701 sprintf(programVersion, "%s", PACKAGE_STRING);
703 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
710 UnloadEngine(ChessProgramState *cps)
712 /* Kill off first chess program */
713 if (cps->isr != NULL)
714 RemoveInputSource(cps->isr);
717 if (cps->pr != NoProc) {
719 DoSleep( appData.delayBeforeQuit );
720 SendToProgram("quit\n", cps);
721 DoSleep( appData.delayAfterQuit );
722 DestroyChildProcess(cps->pr, cps->useSigterm);
725 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
729 ClearOptions(ChessProgramState *cps)
732 cps->nrOptions = cps->comboCnt = 0;
733 for(i=0; i<MAX_OPTIONS; i++) {
734 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735 cps->option[i].textValue = 0;
739 char *engineNames[] = {
745 InitEngine(ChessProgramState *cps, int n)
746 { // [HGM] all engine initialiation put in a function that does one engine
750 cps->which = engineNames[n];
751 cps->maybeThinking = FALSE;
755 cps->sendDrawOffers = 1;
757 cps->program = appData.chessProgram[n];
758 cps->host = appData.host[n];
759 cps->dir = appData.directory[n];
760 cps->initString = appData.engInitString[n];
761 cps->computerString = appData.computerString[n];
762 cps->useSigint = TRUE;
763 cps->useSigterm = TRUE;
764 cps->reuse = appData.reuse[n];
765 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
766 cps->useSetboard = FALSE;
768 cps->usePing = FALSE;
771 cps->usePlayother = FALSE;
772 cps->useColors = TRUE;
773 cps->useUsermove = FALSE;
774 cps->sendICS = FALSE;
775 cps->sendName = appData.icsActive;
776 cps->sdKludge = FALSE;
777 cps->stKludge = FALSE;
778 TidyProgramName(cps->program, cps->host, cps->tidy);
780 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781 cps->analysisSupport = 2; /* detect */
782 cps->analyzing = FALSE;
783 cps->initDone = FALSE;
785 /* New features added by Tord: */
786 cps->useFEN960 = FALSE;
787 cps->useOOCastle = TRUE;
788 /* End of new features added by Tord. */
789 cps->fenOverride = appData.fenOverride[n];
791 /* [HGM] time odds: set factor for each machine */
792 cps->timeOdds = appData.timeOdds[n];
794 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795 cps->accumulateTC = appData.accumulateTC[n];
796 cps->maxNrOfSessions = 1;
801 cps->supportsNPS = UNKNOWN;
802 cps->memSize = FALSE;
803 cps->maxCores = FALSE;
804 cps->egtFormats[0] = NULLCHAR;
807 cps->optionSettings = appData.engOptions[n];
809 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810 cps->isUCI = appData.isUCI[n]; /* [AS] */
811 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813 if (appData.protocolVersion[n] > PROTOVER
814 || appData.protocolVersion[n] < 1)
819 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820 appData.protocolVersion[n]);
821 if( (len > MSG_SIZ) && appData.debugMode )
822 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824 DisplayFatalError(buf, 0, 2);
828 cps->protocolVersion = appData.protocolVersion[n];
831 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ChessProgramState *savCps;
840 if(WaitForEngine(savCps, LoadEngine)) return;
841 CommonEngineInit(); // recalculate time odds
842 if(gameInfo.variant != StringToVariant(appData.variant)) {
843 // we changed variant when loading the engine; this forces us to reset
844 Reset(TRUE, savCps != &first);
845 EditGameEvent(); // for consistency with other path, as Reset changes mode
847 InitChessProgram(savCps, FALSE);
848 SendToProgram("force\n", savCps);
849 DisplayMessage("", "");
850 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
857 ReplaceEngine(ChessProgramState *cps, int n)
861 appData.noChessProgram = FALSE;
862 appData.clockMode = TRUE;
864 if(n) return; // only startup first engine immediately; second can wait
865 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872 static char resetOptions[] =
873 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 Load(ChessProgramState *cps, int i)
879 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
880 if(engineLine[0]) { // an engine was selected from the combo box
881 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
900 snprintf(command, MSG_SIZ, "%s %s", p, params);
903 appData.chessProgram[i] = strdup(p);
904 appData.isUCI[i] = isUCI;
905 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906 appData.hasOwnBookUCI[i] = hasBook;
907 if(!nickName[0]) useNick = FALSE;
908 if(useNick) ASSIGN(appData.pgnName[i], nickName);
911 q = firstChessProgramNames;
912 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
914 useNick ? " -fn \"" : "",
915 useNick ? nickName : "",
917 v1 ? " -firstProtocolVersion 1" : "",
918 hasBook ? "" : " -fNoOwnBookUCI",
919 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920 storeVariant ? " -variant " : "",
921 storeVariant ? VariantName(gameInfo.variant) : "");
922 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
926 ReplaceEngine(cps, i);
932 int matched, min, sec;
934 * Parse timeControl resource
936 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937 appData.movesPerSession)) {
939 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940 DisplayFatalError(buf, 0, 2);
944 * Parse searchTime resource
946 if (*appData.searchTime != NULLCHAR) {
947 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
949 searchTime = min * 60;
950 } else if (matched == 2) {
951 searchTime = min * 60 + sec;
954 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955 DisplayFatalError(buf, 0, 2);
964 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
967 GetTimeMark(&programStartTime);
968 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
972 programStats.ok_to_send = 1;
973 programStats.seen_stat = 0;
976 * Initialize game list
982 * Internet chess server status
984 if (appData.icsActive) {
985 appData.matchMode = FALSE;
986 appData.matchGames = 0;
988 appData.noChessProgram = !appData.zippyPlay;
990 appData.zippyPlay = FALSE;
991 appData.zippyTalk = FALSE;
992 appData.noChessProgram = TRUE;
994 if (*appData.icsHelper != NULLCHAR) {
995 appData.useTelnet = TRUE;
996 appData.telnetProgram = appData.icsHelper;
999 appData.zippyTalk = appData.zippyPlay = FALSE;
1002 /* [AS] Initialize pv info list [HGM] and game state */
1006 for( i=0; i<=framePtr; i++ ) {
1007 pvInfoList[i].depth = -1;
1008 boards[i][EP_STATUS] = EP_NONE;
1009 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015 /* [AS] Adjudication threshold */
1016 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1018 InitEngine(&first, 0);
1019 InitEngine(&second, 1);
1022 pairing.which = "pairing"; // pairing engine
1023 pairing.pr = NoProc;
1025 pairing.program = appData.pairingEngine;
1026 pairing.host = "localhost";
1029 if (appData.icsActive) {
1030 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1031 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1032 appData.clockMode = FALSE;
1033 first.sendTime = second.sendTime = 0;
1037 /* Override some settings from environment variables, for backward
1038 compatibility. Unfortunately it's not feasible to have the env
1039 vars just set defaults, at least in xboard. Ugh.
1041 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046 if (!appData.icsActive) {
1050 /* Check for variants that are supported only in ICS mode,
1051 or not at all. Some that are accepted here nevertheless
1052 have bugs; see comments below.
1054 VariantClass variant = StringToVariant(appData.variant);
1056 case VariantBughouse: /* need four players and two boards */
1057 case VariantKriegspiel: /* need to hide pieces and move details */
1058 /* case VariantFischeRandom: (Fabien: moved below) */
1059 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1060 if( (len > MSG_SIZ) && appData.debugMode )
1061 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1063 DisplayFatalError(buf, 0, 2);
1066 case VariantUnknown:
1067 case VariantLoadable:
1077 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1078 if( (len > MSG_SIZ) && appData.debugMode )
1079 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1081 DisplayFatalError(buf, 0, 2);
1084 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1085 case VariantFairy: /* [HGM] TestLegality definitely off! */
1086 case VariantGothic: /* [HGM] should work */
1087 case VariantCapablanca: /* [HGM] should work */
1088 case VariantCourier: /* [HGM] initial forced moves not implemented */
1089 case VariantShogi: /* [HGM] could still mate with pawn drop */
1090 case VariantKnightmate: /* [HGM] should work */
1091 case VariantCylinder: /* [HGM] untested */
1092 case VariantFalcon: /* [HGM] untested */
1093 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1094 offboard interposition not understood */
1095 case VariantNormal: /* definitely works! */
1096 case VariantWildCastle: /* pieces not automatically shuffled */
1097 case VariantNoCastle: /* pieces not automatically shuffled */
1098 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1099 case VariantLosers: /* should work except for win condition,
1100 and doesn't know captures are mandatory */
1101 case VariantSuicide: /* should work except for win condition,
1102 and doesn't know captures are mandatory */
1103 case VariantGiveaway: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantTwoKings: /* should work */
1106 case VariantAtomic: /* should work except for win condition */
1107 case Variant3Check: /* should work except for win condition */
1108 case VariantShatranj: /* should work except for all win conditions */
1109 case VariantMakruk: /* should work except for daw countdown */
1110 case VariantBerolina: /* might work if TestLegality is off */
1111 case VariantCapaRandom: /* should work */
1112 case VariantJanus: /* should work */
1113 case VariantSuper: /* experimental */
1114 case VariantGreat: /* experimental, requires legality testing to be off */
1115 case VariantSChess: /* S-Chess, should work */
1116 case VariantSpartan: /* should work */
1123 int NextIntegerFromString( char ** str, long * value )
1128 while( *s == ' ' || *s == '\t' ) {
1134 if( *s >= '0' && *s <= '9' ) {
1135 while( *s >= '0' && *s <= '9' ) {
1136 *value = *value * 10 + (*s - '0');
1148 int NextTimeControlFromString( char ** str, long * value )
1151 int result = NextIntegerFromString( str, &temp );
1154 *value = temp * 60; /* Minutes */
1155 if( **str == ':' ) {
1157 result = NextIntegerFromString( str, &temp );
1158 *value += temp; /* Seconds */
1165 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1166 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1167 int result = -1, type = 0; long temp, temp2;
1169 if(**str != ':') return -1; // old params remain in force!
1171 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1172 if( NextIntegerFromString( str, &temp ) ) return -1;
1173 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1176 /* time only: incremental or sudden-death time control */
1177 if(**str == '+') { /* increment follows; read it */
1179 if(**str == '!') type = *(*str)++; // Bronstein TC
1180 if(result = NextIntegerFromString( str, &temp2)) return -1;
1181 *inc = temp2 * 1000;
1182 if(**str == '.') { // read fraction of increment
1183 char *start = ++(*str);
1184 if(result = NextIntegerFromString( str, &temp2)) return -1;
1186 while(start++ < *str) temp2 /= 10;
1190 *moves = 0; *tc = temp * 1000; *incType = type;
1194 (*str)++; /* classical time control */
1195 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1206 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1207 { /* [HGM] get time to add from the multi-session time-control string */
1208 int incType, moves=1; /* kludge to force reading of first session */
1209 long time, increment;
1212 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1213 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1215 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1216 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1217 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1218 if(movenr == -1) return time; /* last move before new session */
1219 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1220 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1221 if(!moves) return increment; /* current session is incremental */
1222 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1223 } while(movenr >= -1); /* try again for next session */
1225 return 0; // no new time quota on this move
1229 ParseTimeControl(tc, ti, mps)
1236 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1239 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1240 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1241 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1245 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1247 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1250 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1252 snprintf(buf, MSG_SIZ, ":%s", mytc);
1254 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1256 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1261 /* Parse second time control */
1264 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1272 timeControl_2 = tc2 * 1000;
1282 timeControl = tc1 * 1000;
1285 timeIncrement = ti * 1000; /* convert to ms */
1286 movesPerSession = 0;
1289 movesPerSession = mps;
1297 if (appData.debugMode) {
1298 fprintf(debugFP, "%s\n", programVersion);
1301 set_cont_sequence(appData.wrapContSeq);
1302 if (appData.matchGames > 0) {
1303 appData.matchMode = TRUE;
1304 } else if (appData.matchMode) {
1305 appData.matchGames = 1;
1307 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1308 appData.matchGames = appData.sameColorGames;
1309 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1310 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1311 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1314 if (appData.noChessProgram || first.protocolVersion == 1) {
1317 /* kludge: allow timeout for initial "feature" commands */
1319 DisplayMessage("", _("Starting chess program"));
1320 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1325 CalculateIndex(int index, int gameNr)
1326 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1328 if(index > 0) return index; // fixed nmber
1329 if(index == 0) return 1;
1330 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1331 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1336 LoadGameOrPosition(int gameNr)
1337 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1338 if (*appData.loadGameFile != NULLCHAR) {
1339 if (!LoadGameFromFile(appData.loadGameFile,
1340 CalculateIndex(appData.loadGameIndex, gameNr),
1341 appData.loadGameFile, FALSE)) {
1342 DisplayFatalError(_("Bad game file"), 0, 1);
1345 } else if (*appData.loadPositionFile != NULLCHAR) {
1346 if (!LoadPositionFromFile(appData.loadPositionFile,
1347 CalculateIndex(appData.loadPositionIndex, gameNr),
1348 appData.loadPositionFile)) {
1349 DisplayFatalError(_("Bad position file"), 0, 1);
1357 ReserveGame(int gameNr, char resChar)
1359 FILE *tf = fopen(appData.tourneyFile, "r+");
1360 char *p, *q, c, buf[MSG_SIZ];
1361 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1362 safeStrCpy(buf, lastMsg, MSG_SIZ);
1363 DisplayMessage(_("Pick new game"), "");
1364 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1365 ParseArgsFromFile(tf);
1366 p = q = appData.results;
1367 if(appData.debugMode) {
1368 char *r = appData.participants;
1369 fprintf(debugFP, "results = '%s'\n", p);
1370 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1371 fprintf(debugFP, "\n");
1373 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1375 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1376 safeStrCpy(q, p, strlen(p) + 2);
1377 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1378 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1379 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1380 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1383 fseek(tf, -(strlen(p)+4), SEEK_END);
1385 if(c != '"') // depending on DOS or Unix line endings we can be one off
1386 fseek(tf, -(strlen(p)+2), SEEK_END);
1387 else fseek(tf, -(strlen(p)+3), SEEK_END);
1388 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1389 DisplayMessage(buf, "");
1390 free(p); appData.results = q;
1391 if(nextGame <= appData.matchGames && resChar != ' ' &&
1392 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1393 UnloadEngine(&first); // next game belongs to other pairing;
1394 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1399 MatchEvent(int mode)
1400 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1402 if(matchMode) { // already in match mode: switch it off
1404 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1405 ModeHighlight(); // kludgey way to remove checkmark...
1408 // if(gameMode != BeginningOfGame) {
1409 // DisplayError(_("You can only start a match from the initial position."), 0);
1413 appData.matchGames = appData.defaultMatchGames;
1414 /* Set up machine vs. machine match */
1416 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1417 if(appData.tourneyFile[0]) {
1419 if(nextGame > appData.matchGames) {
1421 if(strchr(appData.results, '*') == NULL) {
1423 appData.tourneyCycles++;
1424 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1426 NextTourneyGame(-1, &dummy);
1428 if(nextGame <= appData.matchGames) {
1429 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1431 ScheduleDelayedEvent(NextMatchGame, 10000);
1436 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1437 DisplayError(buf, 0);
1438 appData.tourneyFile[0] = 0;
1442 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1443 DisplayFatalError(_("Can't have a match with no chess programs"),
1448 matchGame = roundNr = 1;
1449 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1454 InitBackEnd3 P((void))
1456 GameMode initialMode;
1460 InitChessProgram(&first, startedFromSetupPosition);
1462 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1463 free(programVersion);
1464 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1465 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1468 if (appData.icsActive) {
1470 /* [DM] Make a console window if needed [HGM] merged ifs */
1476 if (*appData.icsCommPort != NULLCHAR)
1477 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1478 appData.icsCommPort);
1480 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1481 appData.icsHost, appData.icsPort);
1483 if( (len > MSG_SIZ) && appData.debugMode )
1484 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1486 DisplayFatalError(buf, err, 1);
1491 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1493 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1494 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1495 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1496 } else if (appData.noChessProgram) {
1502 if (*appData.cmailGameName != NULLCHAR) {
1504 OpenLoopback(&cmailPR);
1506 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1510 DisplayMessage("", "");
1511 if (StrCaseCmp(appData.initialMode, "") == 0) {
1512 initialMode = BeginningOfGame;
1513 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1514 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1515 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1516 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1519 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1520 initialMode = TwoMachinesPlay;
1521 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1522 initialMode = AnalyzeFile;
1523 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1524 initialMode = AnalyzeMode;
1525 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1526 initialMode = MachinePlaysWhite;
1527 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1528 initialMode = MachinePlaysBlack;
1529 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1530 initialMode = EditGame;
1531 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1532 initialMode = EditPosition;
1533 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1534 initialMode = Training;
1536 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1537 if( (len > MSG_SIZ) && appData.debugMode )
1538 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1540 DisplayFatalError(buf, 0, 2);
1544 if (appData.matchMode) {
1545 if(appData.tourneyFile[0]) { // start tourney from command line
1547 if(f = fopen(appData.tourneyFile, "r")) {
1548 ParseArgsFromFile(f); // make sure tourney parmeters re known
1550 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1553 } else if (*appData.cmailGameName != NULLCHAR) {
1554 /* Set up cmail mode */
1555 ReloadCmailMsgEvent(TRUE);
1557 /* Set up other modes */
1558 if (initialMode == AnalyzeFile) {
1559 if (*appData.loadGameFile == NULLCHAR) {
1560 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1564 if (*appData.loadGameFile != NULLCHAR) {
1565 (void) LoadGameFromFile(appData.loadGameFile,
1566 appData.loadGameIndex,
1567 appData.loadGameFile, TRUE);
1568 } else if (*appData.loadPositionFile != NULLCHAR) {
1569 (void) LoadPositionFromFile(appData.loadPositionFile,
1570 appData.loadPositionIndex,
1571 appData.loadPositionFile);
1572 /* [HGM] try to make self-starting even after FEN load */
1573 /* to allow automatic setup of fairy variants with wtm */
1574 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1575 gameMode = BeginningOfGame;
1576 setboardSpoiledMachineBlack = 1;
1578 /* [HGM] loadPos: make that every new game uses the setup */
1579 /* from file as long as we do not switch variant */
1580 if(!blackPlaysFirst) {
1581 startedFromPositionFile = TRUE;
1582 CopyBoard(filePosition, boards[0]);
1585 if (initialMode == AnalyzeMode) {
1586 if (appData.noChessProgram) {
1587 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1590 if (appData.icsActive) {
1591 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1595 } else if (initialMode == AnalyzeFile) {
1596 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1597 ShowThinkingEvent();
1599 AnalysisPeriodicEvent(1);
1600 } else if (initialMode == MachinePlaysWhite) {
1601 if (appData.noChessProgram) {
1602 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1606 if (appData.icsActive) {
1607 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1611 MachineWhiteEvent();
1612 } else if (initialMode == MachinePlaysBlack) {
1613 if (appData.noChessProgram) {
1614 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1618 if (appData.icsActive) {
1619 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1623 MachineBlackEvent();
1624 } else if (initialMode == TwoMachinesPlay) {
1625 if (appData.noChessProgram) {
1626 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1630 if (appData.icsActive) {
1631 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1636 } else if (initialMode == EditGame) {
1638 } else if (initialMode == EditPosition) {
1639 EditPositionEvent();
1640 } else if (initialMode == Training) {
1641 if (*appData.loadGameFile == NULLCHAR) {
1642 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1651 * Establish will establish a contact to a remote host.port.
1652 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1653 * used to talk to the host.
1654 * Returns 0 if okay, error code if not.
1661 if (*appData.icsCommPort != NULLCHAR) {
1662 /* Talk to the host through a serial comm port */
1663 return OpenCommPort(appData.icsCommPort, &icsPR);
1665 } else if (*appData.gateway != NULLCHAR) {
1666 if (*appData.remoteShell == NULLCHAR) {
1667 /* Use the rcmd protocol to run telnet program on a gateway host */
1668 snprintf(buf, sizeof(buf), "%s %s %s",
1669 appData.telnetProgram, appData.icsHost, appData.icsPort);
1670 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1673 /* Use the rsh program to run telnet program on a gateway host */
1674 if (*appData.remoteUser == NULLCHAR) {
1675 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1676 appData.gateway, appData.telnetProgram,
1677 appData.icsHost, appData.icsPort);
1679 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1680 appData.remoteShell, appData.gateway,
1681 appData.remoteUser, appData.telnetProgram,
1682 appData.icsHost, appData.icsPort);
1684 return StartChildProcess(buf, "", &icsPR);
1687 } else if (appData.useTelnet) {
1688 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1691 /* TCP socket interface differs somewhat between
1692 Unix and NT; handle details in the front end.
1694 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1698 void EscapeExpand(char *p, char *q)
1699 { // [HGM] initstring: routine to shape up string arguments
1700 while(*p++ = *q++) if(p[-1] == '\\')
1702 case 'n': p[-1] = '\n'; break;
1703 case 'r': p[-1] = '\r'; break;
1704 case 't': p[-1] = '\t'; break;
1705 case '\\': p[-1] = '\\'; break;
1706 case 0: *p = 0; return;
1707 default: p[-1] = q[-1]; break;
1712 show_bytes(fp, buf, count)
1718 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1719 fprintf(fp, "\\%03o", *buf & 0xff);
1728 /* Returns an errno value */
1730 OutputMaybeTelnet(pr, message, count, outError)
1736 char buf[8192], *p, *q, *buflim;
1737 int left, newcount, outcount;
1739 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1740 *appData.gateway != NULLCHAR) {
1741 if (appData.debugMode) {
1742 fprintf(debugFP, ">ICS: ");
1743 show_bytes(debugFP, message, count);
1744 fprintf(debugFP, "\n");
1746 return OutputToProcess(pr, message, count, outError);
1749 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1756 if (appData.debugMode) {
1757 fprintf(debugFP, ">ICS: ");
1758 show_bytes(debugFP, buf, newcount);
1759 fprintf(debugFP, "\n");
1761 outcount = OutputToProcess(pr, buf, newcount, outError);
1762 if (outcount < newcount) return -1; /* to be sure */
1769 } else if (((unsigned char) *p) == TN_IAC) {
1770 *q++ = (char) TN_IAC;
1777 if (appData.debugMode) {
1778 fprintf(debugFP, ">ICS: ");
1779 show_bytes(debugFP, buf, newcount);
1780 fprintf(debugFP, "\n");
1782 outcount = OutputToProcess(pr, buf, newcount, outError);
1783 if (outcount < newcount) return -1; /* to be sure */
1788 read_from_player(isr, closure, message, count, error)
1795 int outError, outCount;
1796 static int gotEof = 0;
1798 /* Pass data read from player on to ICS */
1801 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1802 if (outCount < count) {
1803 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1805 } else if (count < 0) {
1806 RemoveInputSource(isr);
1807 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1808 } else if (gotEof++ > 0) {
1809 RemoveInputSource(isr);
1810 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1816 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1817 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1818 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1819 SendToICS("date\n");
1820 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1823 /* added routine for printf style output to ics */
1824 void ics_printf(char *format, ...)
1826 char buffer[MSG_SIZ];
1829 va_start(args, format);
1830 vsnprintf(buffer, sizeof(buffer), format, args);
1831 buffer[sizeof(buffer)-1] = '\0';
1840 int count, outCount, outError;
1842 if (icsPR == NULL) return;
1845 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1846 if (outCount < count) {
1847 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1851 /* This is used for sending logon scripts to the ICS. Sending
1852 without a delay causes problems when using timestamp on ICC
1853 (at least on my machine). */
1855 SendToICSDelayed(s,msdelay)
1859 int count, outCount, outError;
1861 if (icsPR == NULL) return;
1864 if (appData.debugMode) {
1865 fprintf(debugFP, ">ICS: ");
1866 show_bytes(debugFP, s, count);
1867 fprintf(debugFP, "\n");
1869 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1871 if (outCount < count) {
1872 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1877 /* Remove all highlighting escape sequences in s
1878 Also deletes any suffix starting with '('
1881 StripHighlightAndTitle(s)
1884 static char retbuf[MSG_SIZ];
1887 while (*s != NULLCHAR) {
1888 while (*s == '\033') {
1889 while (*s != NULLCHAR && !isalpha(*s)) s++;
1890 if (*s != NULLCHAR) s++;
1892 while (*s != NULLCHAR && *s != '\033') {
1893 if (*s == '(' || *s == '[') {
1904 /* Remove all highlighting escape sequences in s */
1909 static char retbuf[MSG_SIZ];
1912 while (*s != NULLCHAR) {
1913 while (*s == '\033') {
1914 while (*s != NULLCHAR && !isalpha(*s)) s++;
1915 if (*s != NULLCHAR) s++;
1917 while (*s != NULLCHAR && *s != '\033') {
1925 char *variantNames[] = VARIANT_NAMES;
1930 return variantNames[v];
1934 /* Identify a variant from the strings the chess servers use or the
1935 PGN Variant tag names we use. */
1942 VariantClass v = VariantNormal;
1943 int i, found = FALSE;
1949 /* [HGM] skip over optional board-size prefixes */
1950 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1951 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1952 while( *e++ != '_');
1955 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1959 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1960 if (StrCaseStr(e, variantNames[i])) {
1961 v = (VariantClass) i;
1968 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1969 || StrCaseStr(e, "wild/fr")
1970 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1971 v = VariantFischeRandom;
1972 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1973 (i = 1, p = StrCaseStr(e, "w"))) {
1975 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1982 case 0: /* FICS only, actually */
1984 /* Castling legal even if K starts on d-file */
1985 v = VariantWildCastle;
1990 /* Castling illegal even if K & R happen to start in
1991 normal positions. */
1992 v = VariantNoCastle;
2005 /* Castling legal iff K & R start in normal positions */
2011 /* Special wilds for position setup; unclear what to do here */
2012 v = VariantLoadable;
2015 /* Bizarre ICC game */
2016 v = VariantTwoKings;
2019 v = VariantKriegspiel;
2025 v = VariantFischeRandom;
2028 v = VariantCrazyhouse;
2031 v = VariantBughouse;
2037 /* Not quite the same as FICS suicide! */
2038 v = VariantGiveaway;
2044 v = VariantShatranj;
2047 /* Temporary names for future ICC types. The name *will* change in
2048 the next xboard/WinBoard release after ICC defines it. */
2086 v = VariantCapablanca;
2089 v = VariantKnightmate;
2095 v = VariantCylinder;
2101 v = VariantCapaRandom;
2104 v = VariantBerolina;
2116 /* Found "wild" or "w" in the string but no number;
2117 must assume it's normal chess. */
2121 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2122 if( (len > MSG_SIZ) && appData.debugMode )
2123 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2125 DisplayError(buf, 0);
2131 if (appData.debugMode) {
2132 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2133 e, wnum, VariantName(v));
2138 static int leftover_start = 0, leftover_len = 0;
2139 char star_match[STAR_MATCH_N][MSG_SIZ];
2141 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2142 advance *index beyond it, and set leftover_start to the new value of
2143 *index; else return FALSE. If pattern contains the character '*', it
2144 matches any sequence of characters not containing '\r', '\n', or the
2145 character following the '*' (if any), and the matched sequence(s) are
2146 copied into star_match.
2149 looking_at(buf, index, pattern)
2154 char *bufp = &buf[*index], *patternp = pattern;
2156 char *matchp = star_match[0];
2159 if (*patternp == NULLCHAR) {
2160 *index = leftover_start = bufp - buf;
2164 if (*bufp == NULLCHAR) return FALSE;
2165 if (*patternp == '*') {
2166 if (*bufp == *(patternp + 1)) {
2168 matchp = star_match[++star_count];
2172 } else if (*bufp == '\n' || *bufp == '\r') {
2174 if (*patternp == NULLCHAR)
2179 *matchp++ = *bufp++;
2183 if (*patternp != *bufp) return FALSE;
2190 SendToPlayer(data, length)
2194 int error, outCount;
2195 outCount = OutputToProcess(NoProc, data, length, &error);
2196 if (outCount < length) {
2197 DisplayFatalError(_("Error writing to display"), error, 1);
2202 PackHolding(packed, holding)
2214 switch (runlength) {
2225 sprintf(q, "%d", runlength);
2237 /* Telnet protocol requests from the front end */
2239 TelnetRequest(ddww, option)
2240 unsigned char ddww, option;
2242 unsigned char msg[3];
2243 int outCount, outError;
2245 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2247 if (appData.debugMode) {
2248 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2264 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2273 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2276 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2281 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2283 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2290 if (!appData.icsActive) return;
2291 TelnetRequest(TN_DO, TN_ECHO);
2297 if (!appData.icsActive) return;
2298 TelnetRequest(TN_DONT, TN_ECHO);
2302 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2304 /* put the holdings sent to us by the server on the board holdings area */
2305 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2309 if(gameInfo.holdingsWidth < 2) return;
2310 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2311 return; // prevent overwriting by pre-board holdings
2313 if( (int)lowestPiece >= BlackPawn ) {
2316 holdingsStartRow = BOARD_HEIGHT-1;
2319 holdingsColumn = BOARD_WIDTH-1;
2320 countsColumn = BOARD_WIDTH-2;
2321 holdingsStartRow = 0;
2325 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2326 board[i][holdingsColumn] = EmptySquare;
2327 board[i][countsColumn] = (ChessSquare) 0;
2329 while( (p=*holdings++) != NULLCHAR ) {
2330 piece = CharToPiece( ToUpper(p) );
2331 if(piece == EmptySquare) continue;
2332 /*j = (int) piece - (int) WhitePawn;*/
2333 j = PieceToNumber(piece);
2334 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2335 if(j < 0) continue; /* should not happen */
2336 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2337 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2338 board[holdingsStartRow+j*direction][countsColumn]++;
2344 VariantSwitch(Board board, VariantClass newVariant)
2346 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2347 static Board oldBoard;
2349 startedFromPositionFile = FALSE;
2350 if(gameInfo.variant == newVariant) return;
2352 /* [HGM] This routine is called each time an assignment is made to
2353 * gameInfo.variant during a game, to make sure the board sizes
2354 * are set to match the new variant. If that means adding or deleting
2355 * holdings, we shift the playing board accordingly
2356 * This kludge is needed because in ICS observe mode, we get boards
2357 * of an ongoing game without knowing the variant, and learn about the
2358 * latter only later. This can be because of the move list we requested,
2359 * in which case the game history is refilled from the beginning anyway,
2360 * but also when receiving holdings of a crazyhouse game. In the latter
2361 * case we want to add those holdings to the already received position.
2365 if (appData.debugMode) {
2366 fprintf(debugFP, "Switch board from %s to %s\n",
2367 VariantName(gameInfo.variant), VariantName(newVariant));
2368 setbuf(debugFP, NULL);
2370 shuffleOpenings = 0; /* [HGM] shuffle */
2371 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2375 newWidth = 9; newHeight = 9;
2376 gameInfo.holdingsSize = 7;
2377 case VariantBughouse:
2378 case VariantCrazyhouse:
2379 newHoldingsWidth = 2; break;
2383 newHoldingsWidth = 2;
2384 gameInfo.holdingsSize = 8;
2387 case VariantCapablanca:
2388 case VariantCapaRandom:
2391 newHoldingsWidth = gameInfo.holdingsSize = 0;
2394 if(newWidth != gameInfo.boardWidth ||
2395 newHeight != gameInfo.boardHeight ||
2396 newHoldingsWidth != gameInfo.holdingsWidth ) {
2398 /* shift position to new playing area, if needed */
2399 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2400 for(i=0; i<BOARD_HEIGHT; i++)
2401 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2402 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2404 for(i=0; i<newHeight; i++) {
2405 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2406 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2408 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2409 for(i=0; i<BOARD_HEIGHT; i++)
2410 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2411 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2414 gameInfo.boardWidth = newWidth;
2415 gameInfo.boardHeight = newHeight;
2416 gameInfo.holdingsWidth = newHoldingsWidth;
2417 gameInfo.variant = newVariant;
2418 InitDrawingSizes(-2, 0);
2419 } else gameInfo.variant = newVariant;
2420 CopyBoard(oldBoard, board); // remember correctly formatted board
2421 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2422 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2425 static int loggedOn = FALSE;
2427 /*-- Game start info cache: --*/
2429 char gs_kind[MSG_SIZ];
2430 static char player1Name[128] = "";
2431 static char player2Name[128] = "";
2432 static char cont_seq[] = "\n\\ ";
2433 static int player1Rating = -1;
2434 static int player2Rating = -1;
2435 /*----------------------------*/
2437 ColorClass curColor = ColorNormal;
2438 int suppressKibitz = 0;
2441 Boolean soughtPending = FALSE;
2442 Boolean seekGraphUp;
2443 #define MAX_SEEK_ADS 200
2445 char *seekAdList[MAX_SEEK_ADS];
2446 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2447 float tcList[MAX_SEEK_ADS];
2448 char colorList[MAX_SEEK_ADS];
2449 int nrOfSeekAds = 0;
2450 int minRating = 1010, maxRating = 2800;
2451 int hMargin = 10, vMargin = 20, h, w;
2452 extern int squareSize, lineGap;
2457 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2458 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2459 if(r < minRating+100 && r >=0 ) r = minRating+100;
2460 if(r > maxRating) r = maxRating;
2461 if(tc < 1.) tc = 1.;
2462 if(tc > 95.) tc = 95.;
2463 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2464 y = ((double)r - minRating)/(maxRating - minRating)
2465 * (h-vMargin-squareSize/8-1) + vMargin;
2466 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2467 if(strstr(seekAdList[i], " u ")) color = 1;
2468 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2469 !strstr(seekAdList[i], "bullet") &&
2470 !strstr(seekAdList[i], "blitz") &&
2471 !strstr(seekAdList[i], "standard") ) color = 2;
2472 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2473 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2477 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2479 char buf[MSG_SIZ], *ext = "";
2480 VariantClass v = StringToVariant(type);
2481 if(strstr(type, "wild")) {
2482 ext = type + 4; // append wild number
2483 if(v == VariantFischeRandom) type = "chess960"; else
2484 if(v == VariantLoadable) type = "setup"; else
2485 type = VariantName(v);
2487 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2488 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2489 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2490 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2491 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2492 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2493 seekNrList[nrOfSeekAds] = nr;
2494 zList[nrOfSeekAds] = 0;
2495 seekAdList[nrOfSeekAds++] = StrSave(buf);
2496 if(plot) PlotSeekAd(nrOfSeekAds-1);
2503 int x = xList[i], y = yList[i], d=squareSize/4, k;
2504 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2505 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2506 // now replot every dot that overlapped
2507 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2508 int xx = xList[k], yy = yList[k];
2509 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2510 DrawSeekDot(xx, yy, colorList[k]);
2515 RemoveSeekAd(int nr)
2518 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2520 if(seekAdList[i]) free(seekAdList[i]);
2521 seekAdList[i] = seekAdList[--nrOfSeekAds];
2522 seekNrList[i] = seekNrList[nrOfSeekAds];
2523 ratingList[i] = ratingList[nrOfSeekAds];
2524 colorList[i] = colorList[nrOfSeekAds];
2525 tcList[i] = tcList[nrOfSeekAds];
2526 xList[i] = xList[nrOfSeekAds];
2527 yList[i] = yList[nrOfSeekAds];
2528 zList[i] = zList[nrOfSeekAds];
2529 seekAdList[nrOfSeekAds] = NULL;
2535 MatchSoughtLine(char *line)
2537 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2538 int nr, base, inc, u=0; char dummy;
2540 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2541 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2543 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2544 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2545 // match: compact and save the line
2546 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2556 if(!seekGraphUp) return FALSE;
2557 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2558 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2560 DrawSeekBackground(0, 0, w, h);
2561 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2562 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2563 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2564 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2566 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2569 snprintf(buf, MSG_SIZ, "%d", i);
2570 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2573 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2574 for(i=1; i<100; i+=(i<10?1:5)) {
2575 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2576 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2577 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2579 snprintf(buf, MSG_SIZ, "%d", i);
2580 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2583 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2587 int SeekGraphClick(ClickType click, int x, int y, int moving)
2589 static int lastDown = 0, displayed = 0, lastSecond;
2590 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2591 if(click == Release || moving) return FALSE;
2593 soughtPending = TRUE;
2594 SendToICS(ics_prefix);
2595 SendToICS("sought\n"); // should this be "sought all"?
2596 } else { // issue challenge based on clicked ad
2597 int dist = 10000; int i, closest = 0, second = 0;
2598 for(i=0; i<nrOfSeekAds; i++) {
2599 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2600 if(d < dist) { dist = d; closest = i; }
2601 second += (d - zList[i] < 120); // count in-range ads
2602 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2606 second = (second > 1);
2607 if(displayed != closest || second != lastSecond) {
2608 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2609 lastSecond = second; displayed = closest;
2611 if(click == Press) {
2612 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2615 } // on press 'hit', only show info
2616 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2617 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2618 SendToICS(ics_prefix);
2620 return TRUE; // let incoming board of started game pop down the graph
2621 } else if(click == Release) { // release 'miss' is ignored
2622 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2623 if(moving == 2) { // right up-click
2624 nrOfSeekAds = 0; // refresh graph
2625 soughtPending = TRUE;
2626 SendToICS(ics_prefix);
2627 SendToICS("sought\n"); // should this be "sought all"?
2630 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2631 // press miss or release hit 'pop down' seek graph
2632 seekGraphUp = FALSE;
2633 DrawPosition(TRUE, NULL);
2639 read_from_ics(isr, closure, data, count, error)
2646 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2647 #define STARTED_NONE 0
2648 #define STARTED_MOVES 1
2649 #define STARTED_BOARD 2
2650 #define STARTED_OBSERVE 3
2651 #define STARTED_HOLDINGS 4
2652 #define STARTED_CHATTER 5
2653 #define STARTED_COMMENT 6
2654 #define STARTED_MOVES_NOHIDE 7
2656 static int started = STARTED_NONE;
2657 static char parse[20000];
2658 static int parse_pos = 0;
2659 static char buf[BUF_SIZE + 1];
2660 static int firstTime = TRUE, intfSet = FALSE;
2661 static ColorClass prevColor = ColorNormal;
2662 static int savingComment = FALSE;
2663 static int cmatch = 0; // continuation sequence match
2670 int backup; /* [DM] For zippy color lines */
2672 char talker[MSG_SIZ]; // [HGM] chat
2675 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2677 if (appData.debugMode) {
2679 fprintf(debugFP, "<ICS: ");
2680 show_bytes(debugFP, data, count);
2681 fprintf(debugFP, "\n");
2685 if (appData.debugMode) { int f = forwardMostMove;
2686 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2687 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2688 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2691 /* If last read ended with a partial line that we couldn't parse,
2692 prepend it to the new read and try again. */
2693 if (leftover_len > 0) {
2694 for (i=0; i<leftover_len; i++)
2695 buf[i] = buf[leftover_start + i];
2698 /* copy new characters into the buffer */
2699 bp = buf + leftover_len;
2700 buf_len=leftover_len;
2701 for (i=0; i<count; i++)
2704 if (data[i] == '\r')
2707 // join lines split by ICS?
2708 if (!appData.noJoin)
2711 Joining just consists of finding matches against the
2712 continuation sequence, and discarding that sequence
2713 if found instead of copying it. So, until a match
2714 fails, there's nothing to do since it might be the
2715 complete sequence, and thus, something we don't want
2718 if (data[i] == cont_seq[cmatch])
2721 if (cmatch == strlen(cont_seq))
2723 cmatch = 0; // complete match. just reset the counter
2726 it's possible for the ICS to not include the space
2727 at the end of the last word, making our [correct]
2728 join operation fuse two separate words. the server
2729 does this when the space occurs at the width setting.
2731 if (!buf_len || buf[buf_len-1] != ' ')
2742 match failed, so we have to copy what matched before
2743 falling through and copying this character. In reality,
2744 this will only ever be just the newline character, but
2745 it doesn't hurt to be precise.
2747 strncpy(bp, cont_seq, cmatch);
2759 buf[buf_len] = NULLCHAR;
2760 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2765 while (i < buf_len) {
2766 /* Deal with part of the TELNET option negotiation
2767 protocol. We refuse to do anything beyond the
2768 defaults, except that we allow the WILL ECHO option,
2769 which ICS uses to turn off password echoing when we are
2770 directly connected to it. We reject this option
2771 if localLineEditing mode is on (always on in xboard)
2772 and we are talking to port 23, which might be a real
2773 telnet server that will try to keep WILL ECHO on permanently.
2775 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2776 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2777 unsigned char option;
2779 switch ((unsigned char) buf[++i]) {
2781 if (appData.debugMode)
2782 fprintf(debugFP, "\n<WILL ");
2783 switch (option = (unsigned char) buf[++i]) {
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ECHO ");
2787 /* Reply only if this is a change, according
2788 to the protocol rules. */
2789 if (remoteEchoOption) break;
2790 if (appData.localLineEditing &&
2791 atoi(appData.icsPort) == TN_PORT) {
2792 TelnetRequest(TN_DONT, TN_ECHO);
2795 TelnetRequest(TN_DO, TN_ECHO);
2796 remoteEchoOption = TRUE;
2800 if (appData.debugMode)
2801 fprintf(debugFP, "%d ", option);
2802 /* Whatever this is, we don't want it. */
2803 TelnetRequest(TN_DONT, option);
2808 if (appData.debugMode)
2809 fprintf(debugFP, "\n<WONT ");
2810 switch (option = (unsigned char) buf[++i]) {
2812 if (appData.debugMode)
2813 fprintf(debugFP, "ECHO ");
2814 /* Reply only if this is a change, according
2815 to the protocol rules. */
2816 if (!remoteEchoOption) break;
2818 TelnetRequest(TN_DONT, TN_ECHO);
2819 remoteEchoOption = FALSE;
2822 if (appData.debugMode)
2823 fprintf(debugFP, "%d ", (unsigned char) option);
2824 /* Whatever this is, it must already be turned
2825 off, because we never agree to turn on
2826 anything non-default, so according to the
2827 protocol rules, we don't reply. */
2832 if (appData.debugMode)
2833 fprintf(debugFP, "\n<DO ");
2834 switch (option = (unsigned char) buf[++i]) {
2836 /* Whatever this is, we refuse to do it. */
2837 if (appData.debugMode)
2838 fprintf(debugFP, "%d ", option);
2839 TelnetRequest(TN_WONT, option);
2844 if (appData.debugMode)
2845 fprintf(debugFP, "\n<DONT ");
2846 switch (option = (unsigned char) buf[++i]) {
2848 if (appData.debugMode)
2849 fprintf(debugFP, "%d ", option);
2850 /* Whatever this is, we are already not doing
2851 it, because we never agree to do anything
2852 non-default, so according to the protocol
2853 rules, we don't reply. */
2858 if (appData.debugMode)
2859 fprintf(debugFP, "\n<IAC ");
2860 /* Doubled IAC; pass it through */
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2866 /* Drop all other telnet commands on the floor */
2869 if (oldi > next_out)
2870 SendToPlayer(&buf[next_out], oldi - next_out);
2876 /* OK, this at least will *usually* work */
2877 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2881 if (loggedOn && !intfSet) {
2882 if (ics_type == ICS_ICC) {
2883 snprintf(str, MSG_SIZ,
2884 "/set-quietly interface %s\n/set-quietly style 12\n",
2886 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887 strcat(str, "/set-2 51 1\n/set seek 1\n");
2888 } else if (ics_type == ICS_CHESSNET) {
2889 snprintf(str, MSG_SIZ, "/style 12\n");
2891 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2892 strcat(str, programVersion);
2893 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2894 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2895 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2897 strcat(str, "$iset nohighlight 1\n");
2899 strcat(str, "$iset lock 1\n$style 12\n");
2902 NotifyFrontendLogin();
2906 if (started == STARTED_COMMENT) {
2907 /* Accumulate characters in comment */
2908 parse[parse_pos++] = buf[i];
2909 if (buf[i] == '\n') {
2910 parse[parse_pos] = NULLCHAR;
2911 if(chattingPartner>=0) {
2913 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2914 OutputChatMessage(chattingPartner, mess);
2915 chattingPartner = -1;
2916 next_out = i+1; // [HGM] suppress printing in ICS window
2918 if(!suppressKibitz) // [HGM] kibitz
2919 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2920 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2921 int nrDigit = 0, nrAlph = 0, j;
2922 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2923 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2924 parse[parse_pos] = NULLCHAR;
2925 // try to be smart: if it does not look like search info, it should go to
2926 // ICS interaction window after all, not to engine-output window.
2927 for(j=0; j<parse_pos; j++) { // count letters and digits
2928 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2929 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2930 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2932 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2933 int depth=0; float score;
2934 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2935 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2936 pvInfoList[forwardMostMove-1].depth = depth;
2937 pvInfoList[forwardMostMove-1].score = 100*score;
2939 OutputKibitz(suppressKibitz, parse);
2942 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2943 SendToPlayer(tmp, strlen(tmp));
2945 next_out = i+1; // [HGM] suppress printing in ICS window
2947 started = STARTED_NONE;
2949 /* Don't match patterns against characters in comment */
2954 if (started == STARTED_CHATTER) {
2955 if (buf[i] != '\n') {
2956 /* Don't match patterns against characters in chatter */
2960 started = STARTED_NONE;
2961 if(suppressKibitz) next_out = i+1;
2964 /* Kludge to deal with rcmd protocol */
2965 if (firstTime && looking_at(buf, &i, "\001*")) {
2966 DisplayFatalError(&buf[1], 0, 1);
2972 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ics_type %d\n", ics_type);
2979 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2980 ics_type = ICS_FICS;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2986 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2987 ics_type = ICS_CHESSNET;
2989 if (appData.debugMode)
2990 fprintf(debugFP, "ics_type %d\n", ics_type);
2995 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2996 looking_at(buf, &i, "Logging you in as \"*\"") ||
2997 looking_at(buf, &i, "will be \"*\""))) {
2998 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3002 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3004 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3005 DisplayIcsInteractionTitle(buf);
3006 have_set_title = TRUE;
3009 /* skip finger notes */
3010 if (started == STARTED_NONE &&
3011 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3012 (buf[i] == '1' && buf[i+1] == '0')) &&
3013 buf[i+2] == ':' && buf[i+3] == ' ') {
3014 started = STARTED_CHATTER;
3020 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3021 if(appData.seekGraph) {
3022 if(soughtPending && MatchSoughtLine(buf+i)) {
3023 i = strstr(buf+i, "rated") - buf;
3024 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3025 next_out = leftover_start = i;
3026 started = STARTED_CHATTER;
3027 suppressKibitz = TRUE;
3030 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3031 && looking_at(buf, &i, "* ads displayed")) {
3032 soughtPending = FALSE;
3037 if(appData.autoRefresh) {
3038 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3039 int s = (ics_type == ICS_ICC); // ICC format differs
3041 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3042 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3043 looking_at(buf, &i, "*% "); // eat prompt
3044 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3045 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046 next_out = i; // suppress
3049 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3050 char *p = star_match[0];
3052 if(seekGraphUp) RemoveSeekAd(atoi(p));
3053 while(*p && *p++ != ' '); // next
3055 looking_at(buf, &i, "*% "); // eat prompt
3056 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063 /* skip formula vars */
3064 if (started == STARTED_NONE &&
3065 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3066 started = STARTED_CHATTER;
3071 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3072 if (appData.autoKibitz && started == STARTED_NONE &&
3073 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3074 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3075 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3078 suppressKibitz = TRUE;
3079 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3081 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082 && (gameMode == IcsPlayingWhite)) ||
3083 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3085 started = STARTED_CHATTER; // own kibitz we simply discard
3087 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088 parse_pos = 0; parse[0] = NULLCHAR;
3089 savingComment = TRUE;
3090 suppressKibitz = gameMode != IcsObserving ? 2 :
3091 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3095 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097 && atoi(star_match[0])) {
3098 // suppress the acknowledgements of our own autoKibitz
3100 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102 SendToPlayer(star_match[0], strlen(star_match[0]));
3103 if(looking_at(buf, &i, "*% ")) // eat prompt
3104 suppressKibitz = FALSE;
3108 } // [HGM] kibitz: end of patch
3110 // [HGM] chat: intercept tells by users for which we have an open chat window
3112 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3113 looking_at(buf, &i, "* whispers:") ||
3114 looking_at(buf, &i, "* kibitzes:") ||
3115 looking_at(buf, &i, "* shouts:") ||
3116 looking_at(buf, &i, "* c-shouts:") ||
3117 looking_at(buf, &i, "--> * ") ||
3118 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3119 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3120 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3121 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3123 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3124 chattingPartner = -1;
3126 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3127 for(p=0; p<MAX_CHAT; p++) {
3128 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3129 talker[0] = '['; strcat(talker, "] ");
3130 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3131 chattingPartner = p; break;
3134 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3135 for(p=0; p<MAX_CHAT; p++) {
3136 if(!strcmp("kibitzes", chatPartner[p])) {
3137 talker[0] = '['; strcat(talker, "] ");
3138 chattingPartner = p; break;
3141 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3142 for(p=0; p<MAX_CHAT; p++) {
3143 if(!strcmp("whispers", chatPartner[p])) {
3144 talker[0] = '['; strcat(talker, "] ");
3145 chattingPartner = p; break;
3148 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3149 if(buf[i-8] == '-' && buf[i-3] == 't')
3150 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3151 if(!strcmp("c-shouts", chatPartner[p])) {
3152 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3153 chattingPartner = p; break;
3156 if(chattingPartner < 0)
3157 for(p=0; p<MAX_CHAT; p++) {
3158 if(!strcmp("shouts", chatPartner[p])) {
3159 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3160 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3161 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3162 chattingPartner = p; break;
3166 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3167 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3168 talker[0] = 0; Colorize(ColorTell, FALSE);
3169 chattingPartner = p; break;
3171 if(chattingPartner<0) i = oldi; else {
3172 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3173 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175 started = STARTED_COMMENT;
3176 parse_pos = 0; parse[0] = NULLCHAR;
3177 savingComment = 3 + chattingPartner; // counts as TRUE
3178 suppressKibitz = TRUE;
3181 } // [HGM] chat: end of patch
3184 if (appData.zippyTalk || appData.zippyPlay) {
3185 /* [DM] Backup address for color zippy lines */
3187 if (loggedOn == TRUE)
3188 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3189 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3191 } // [DM] 'else { ' deleted
3193 /* Regular tells and says */
3194 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3195 looking_at(buf, &i, "* (your partner) tells you: ") ||
3196 looking_at(buf, &i, "* says: ") ||
3197 /* Don't color "message" or "messages" output */
3198 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3199 looking_at(buf, &i, "*. * at *:*: ") ||
3200 looking_at(buf, &i, "--* (*:*): ") ||
3201 /* Message notifications (same color as tells) */
3202 looking_at(buf, &i, "* has left a message ") ||
3203 looking_at(buf, &i, "* just sent you a message:\n") ||
3204 /* Whispers and kibitzes */
3205 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3206 looking_at(buf, &i, "* kibitzes: ") ||
3208 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3210 if (tkind == 1 && strchr(star_match[0], ':')) {
3211 /* Avoid "tells you:" spoofs in channels */
3214 if (star_match[0][0] == NULLCHAR ||
3215 strchr(star_match[0], ' ') ||
3216 (tkind == 3 && strchr(star_match[1], ' '))) {
3217 /* Reject bogus matches */
3220 if (appData.colorize) {
3221 if (oldi > next_out) {
3222 SendToPlayer(&buf[next_out], oldi - next_out);
3227 Colorize(ColorTell, FALSE);
3228 curColor = ColorTell;
3231 Colorize(ColorKibitz, FALSE);
3232 curColor = ColorKibitz;
3235 p = strrchr(star_match[1], '(');
3242 Colorize(ColorChannel1, FALSE);
3243 curColor = ColorChannel1;
3245 Colorize(ColorChannel, FALSE);
3246 curColor = ColorChannel;
3250 curColor = ColorNormal;
3254 if (started == STARTED_NONE && appData.autoComment &&
3255 (gameMode == IcsObserving ||
3256 gameMode == IcsPlayingWhite ||
3257 gameMode == IcsPlayingBlack)) {
3258 parse_pos = i - oldi;
3259 memcpy(parse, &buf[oldi], parse_pos);
3260 parse[parse_pos] = NULLCHAR;
3261 started = STARTED_COMMENT;
3262 savingComment = TRUE;
3264 started = STARTED_CHATTER;
3265 savingComment = FALSE;
3272 if (looking_at(buf, &i, "* s-shouts: ") ||
3273 looking_at(buf, &i, "* c-shouts: ")) {
3274 if (appData.colorize) {
3275 if (oldi > next_out) {
3276 SendToPlayer(&buf[next_out], oldi - next_out);
3279 Colorize(ColorSShout, FALSE);
3280 curColor = ColorSShout;
3283 started = STARTED_CHATTER;
3287 if (looking_at(buf, &i, "--->")) {
3292 if (looking_at(buf, &i, "* shouts: ") ||
3293 looking_at(buf, &i, "--> ")) {
3294 if (appData.colorize) {
3295 if (oldi > next_out) {
3296 SendToPlayer(&buf[next_out], oldi - next_out);
3299 Colorize(ColorShout, FALSE);
3300 curColor = ColorShout;
3303 started = STARTED_CHATTER;
3307 if (looking_at( buf, &i, "Challenge:")) {
3308 if (appData.colorize) {
3309 if (oldi > next_out) {
3310 SendToPlayer(&buf[next_out], oldi - next_out);
3313 Colorize(ColorChallenge, FALSE);
3314 curColor = ColorChallenge;
3320 if (looking_at(buf, &i, "* offers you") ||
3321 looking_at(buf, &i, "* offers to be") ||
3322 looking_at(buf, &i, "* would like to") ||
3323 looking_at(buf, &i, "* requests to") ||
3324 looking_at(buf, &i, "Your opponent offers") ||
3325 looking_at(buf, &i, "Your opponent requests")) {
3327 if (appData.colorize) {
3328 if (oldi > next_out) {
3329 SendToPlayer(&buf[next_out], oldi - next_out);
3332 Colorize(ColorRequest, FALSE);
3333 curColor = ColorRequest;
3338 if (looking_at(buf, &i, "* (*) seeking")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorSeek, FALSE);
3345 curColor = ColorSeek;
3350 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3352 if (looking_at(buf, &i, "\\ ")) {
3353 if (prevColor != ColorNormal) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(prevColor, TRUE);
3359 curColor = prevColor;
3361 if (savingComment) {
3362 parse_pos = i - oldi;
3363 memcpy(parse, &buf[oldi], parse_pos);
3364 parse[parse_pos] = NULLCHAR;
3365 started = STARTED_COMMENT;
3366 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3367 chattingPartner = savingComment - 3; // kludge to remember the box
3369 started = STARTED_CHATTER;
3374 if (looking_at(buf, &i, "Black Strength :") ||
3375 looking_at(buf, &i, "<<< style 10 board >>>") ||
3376 looking_at(buf, &i, "<10>") ||
3377 looking_at(buf, &i, "#@#")) {
3378 /* Wrong board style */
3380 SendToICS(ics_prefix);
3381 SendToICS("set style 12\n");
3382 SendToICS(ics_prefix);
3383 SendToICS("refresh\n");
3387 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3389 have_sent_ICS_logon = 1;
3393 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3394 (looking_at(buf, &i, "\n<12> ") ||
3395 looking_at(buf, &i, "<12> "))) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 started = STARTED_BOARD;
3406 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3407 looking_at(buf, &i, "<b1> ")) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 started = STARTED_HOLDINGS;
3417 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3419 /* Header for a move list -- first line */
3421 switch (ics_getting_history) {
3425 case BeginningOfGame:
3426 /* User typed "moves" or "oldmoves" while we
3427 were idle. Pretend we asked for these
3428 moves and soak them up so user can step
3429 through them and/or save them.
3432 gameMode = IcsObserving;
3435 ics_getting_history = H_GOT_UNREQ_HEADER;
3437 case EditGame: /*?*/
3438 case EditPosition: /*?*/
3439 /* Should above feature work in these modes too? */
3440 /* For now it doesn't */
3441 ics_getting_history = H_GOT_UNWANTED_HEADER;
3444 ics_getting_history = H_GOT_UNWANTED_HEADER;
3449 /* Is this the right one? */
3450 if (gameInfo.white && gameInfo.black &&
3451 strcmp(gameInfo.white, star_match[0]) == 0 &&
3452 strcmp(gameInfo.black, star_match[2]) == 0) {
3454 ics_getting_history = H_GOT_REQ_HEADER;
3457 case H_GOT_REQ_HEADER:
3458 case H_GOT_UNREQ_HEADER:
3459 case H_GOT_UNWANTED_HEADER:
3460 case H_GETTING_MOVES:
3461 /* Should not happen */
3462 DisplayError(_("Error gathering move list: two headers"), 0);
3463 ics_getting_history = H_FALSE;
3467 /* Save player ratings into gameInfo if needed */
3468 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3469 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3470 (gameInfo.whiteRating == -1 ||
3471 gameInfo.blackRating == -1)) {
3473 gameInfo.whiteRating = string_to_rating(star_match[1]);
3474 gameInfo.blackRating = string_to_rating(star_match[3]);
3475 if (appData.debugMode)
3476 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3477 gameInfo.whiteRating, gameInfo.blackRating);
3482 if (looking_at(buf, &i,
3483 "* * match, initial time: * minute*, increment: * second")) {
3484 /* Header for a move list -- second line */
3485 /* Initial board will follow if this is a wild game */
3486 if (gameInfo.event != NULL) free(gameInfo.event);
3487 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3488 gameInfo.event = StrSave(str);
3489 /* [HGM] we switched variant. Translate boards if needed. */
3490 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3494 if (looking_at(buf, &i, "Move ")) {
3495 /* Beginning of a move list */
3496 switch (ics_getting_history) {
3498 /* Normally should not happen */
3499 /* Maybe user hit reset while we were parsing */
3502 /* Happens if we are ignoring a move list that is not
3503 * the one we just requested. Common if the user
3504 * tries to observe two games without turning off
3507 case H_GETTING_MOVES:
3508 /* Should not happen */
3509 DisplayError(_("Error gathering move list: nested"), 0);
3510 ics_getting_history = H_FALSE;
3512 case H_GOT_REQ_HEADER:
3513 ics_getting_history = H_GETTING_MOVES;
3514 started = STARTED_MOVES;
3516 if (oldi > next_out) {
3517 SendToPlayer(&buf[next_out], oldi - next_out);
3520 case H_GOT_UNREQ_HEADER:
3521 ics_getting_history = H_GETTING_MOVES;
3522 started = STARTED_MOVES_NOHIDE;
3525 case H_GOT_UNWANTED_HEADER:
3526 ics_getting_history = H_FALSE;
3532 if (looking_at(buf, &i, "% ") ||
3533 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3534 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3535 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3536 soughtPending = FALSE;
3540 if(suppressKibitz) next_out = i;
3541 savingComment = FALSE;
3545 case STARTED_MOVES_NOHIDE:
3546 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3547 parse[parse_pos + i - oldi] = NULLCHAR;
3548 ParseGameHistory(parse);
3550 if (appData.zippyPlay && first.initDone) {
3551 FeedMovesToProgram(&first, forwardMostMove);
3552 if (gameMode == IcsPlayingWhite) {
3553 if (WhiteOnMove(forwardMostMove)) {
3554 if (first.sendTime) {
3555 if (first.useColors) {
3556 SendToProgram("black\n", &first);
3558 SendTimeRemaining(&first, TRUE);
3560 if (first.useColors) {
3561 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3563 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3564 first.maybeThinking = TRUE;
3566 if (first.usePlayother) {
3567 if (first.sendTime) {
3568 SendTimeRemaining(&first, TRUE);
3570 SendToProgram("playother\n", &first);
3576 } else if (gameMode == IcsPlayingBlack) {
3577 if (!WhiteOnMove(forwardMostMove)) {
3578 if (first.sendTime) {
3579 if (first.useColors) {
3580 SendToProgram("white\n", &first);
3582 SendTimeRemaining(&first, FALSE);
3584 if (first.useColors) {
3585 SendToProgram("black\n", &first);
3587 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3588 first.maybeThinking = TRUE;
3590 if (first.usePlayother) {
3591 if (first.sendTime) {
3592 SendTimeRemaining(&first, FALSE);
3594 SendToProgram("playother\n", &first);
3603 if (gameMode == IcsObserving && ics_gamenum == -1) {
3604 /* Moves came from oldmoves or moves command
3605 while we weren't doing anything else.
3607 currentMove = forwardMostMove;
3608 ClearHighlights();/*!!could figure this out*/
3609 flipView = appData.flipView;
3610 DrawPosition(TRUE, boards[currentMove]);
3611 DisplayBothClocks();
3612 snprintf(str, MSG_SIZ, "%s vs. %s",
3613 gameInfo.white, gameInfo.black);
3617 /* Moves were history of an active game */
3618 if (gameInfo.resultDetails != NULL) {
3619 free(gameInfo.resultDetails);
3620 gameInfo.resultDetails = NULL;
3623 HistorySet(parseList, backwardMostMove,
3624 forwardMostMove, currentMove-1);
3625 DisplayMove(currentMove - 1);
3626 if (started == STARTED_MOVES) next_out = i;
3627 started = STARTED_NONE;
3628 ics_getting_history = H_FALSE;
3631 case STARTED_OBSERVE:
3632 started = STARTED_NONE;
3633 SendToICS(ics_prefix);
3634 SendToICS("refresh\n");
3640 if(bookHit) { // [HGM] book: simulate book reply
3641 static char bookMove[MSG_SIZ]; // a bit generous?
3643 programStats.nodes = programStats.depth = programStats.time =
3644 programStats.score = programStats.got_only_move = 0;
3645 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3647 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3648 strcat(bookMove, bookHit);
3649 HandleMachineMove(bookMove, &first);
3654 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3655 started == STARTED_HOLDINGS ||
3656 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3657 /* Accumulate characters in move list or board */
3658 parse[parse_pos++] = buf[i];
3661 /* Start of game messages. Mostly we detect start of game
3662 when the first board image arrives. On some versions
3663 of the ICS, though, we need to do a "refresh" after starting
3664 to observe in order to get the current board right away. */
3665 if (looking_at(buf, &i, "Adding game * to observation list")) {
3666 started = STARTED_OBSERVE;
3670 /* Handle auto-observe */
3671 if (appData.autoObserve &&
3672 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3673 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3675 /* Choose the player that was highlighted, if any. */
3676 if (star_match[0][0] == '\033' ||
3677 star_match[1][0] != '\033') {
3678 player = star_match[0];
3680 player = star_match[2];
3682 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3683 ics_prefix, StripHighlightAndTitle(player));
3686 /* Save ratings from notify string */
3687 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3688 player1Rating = string_to_rating(star_match[1]);
3689 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3690 player2Rating = string_to_rating(star_match[3]);
3692 if (appData.debugMode)
3694 "Ratings from 'Game notification:' %s %d, %s %d\n",
3695 player1Name, player1Rating,
3696 player2Name, player2Rating);
3701 /* Deal with automatic examine mode after a game,
3702 and with IcsObserving -> IcsExamining transition */
3703 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3704 looking_at(buf, &i, "has made you an examiner of game *")) {
3706 int gamenum = atoi(star_match[0]);
3707 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3708 gamenum == ics_gamenum) {
3709 /* We were already playing or observing this game;
3710 no need to refetch history */
3711 gameMode = IcsExamining;
3713 pauseExamForwardMostMove = forwardMostMove;
3714 } else if (currentMove < forwardMostMove) {
3715 ForwardInner(forwardMostMove);
3718 /* I don't think this case really can happen */
3719 SendToICS(ics_prefix);
3720 SendToICS("refresh\n");
3725 /* Error messages */
3726 // if (ics_user_moved) {
3727 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3728 if (looking_at(buf, &i, "Illegal move") ||
3729 looking_at(buf, &i, "Not a legal move") ||
3730 looking_at(buf, &i, "Your king is in check") ||
3731 looking_at(buf, &i, "It isn't your turn") ||
3732 looking_at(buf, &i, "It is not your move")) {
3734 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3735 currentMove = forwardMostMove-1;
3736 DisplayMove(currentMove - 1); /* before DMError */
3737 DrawPosition(FALSE, boards[currentMove]);
3738 SwitchClocks(forwardMostMove-1); // [HGM] race
3739 DisplayBothClocks();
3741 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3747 if (looking_at(buf, &i, "still have time") ||
3748 looking_at(buf, &i, "not out of time") ||
3749 looking_at(buf, &i, "either player is out of time") ||
3750 looking_at(buf, &i, "has timeseal; checking")) {
3751 /* We must have called his flag a little too soon */
3752 whiteFlag = blackFlag = FALSE;
3756 if (looking_at(buf, &i, "added * seconds to") ||
3757 looking_at(buf, &i, "seconds were added to")) {
3758 /* Update the clocks */
3759 SendToICS(ics_prefix);
3760 SendToICS("refresh\n");
3764 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3765 ics_clock_paused = TRUE;
3770 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3771 ics_clock_paused = FALSE;
3776 /* Grab player ratings from the Creating: message.
3777 Note we have to check for the special case when
3778 the ICS inserts things like [white] or [black]. */
3779 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3780 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3782 0 player 1 name (not necessarily white)
3784 2 empty, white, or black (IGNORED)
3785 3 player 2 name (not necessarily black)
3788 The names/ratings are sorted out when the game
3789 actually starts (below).
3791 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3792 player1Rating = string_to_rating(star_match[1]);
3793 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3794 player2Rating = string_to_rating(star_match[4]);
3796 if (appData.debugMode)
3798 "Ratings from 'Creating:' %s %d, %s %d\n",
3799 player1Name, player1Rating,
3800 player2Name, player2Rating);
3805 /* Improved generic start/end-of-game messages */
3806 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3807 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3808 /* If tkind == 0: */
3809 /* star_match[0] is the game number */
3810 /* [1] is the white player's name */
3811 /* [2] is the black player's name */
3812 /* For end-of-game: */
3813 /* [3] is the reason for the game end */
3814 /* [4] is a PGN end game-token, preceded by " " */
3815 /* For start-of-game: */
3816 /* [3] begins with "Creating" or "Continuing" */
3817 /* [4] is " *" or empty (don't care). */
3818 int gamenum = atoi(star_match[0]);
3819 char *whitename, *blackname, *why, *endtoken;
3820 ChessMove endtype = EndOfFile;
3823 whitename = star_match[1];
3824 blackname = star_match[2];
3825 why = star_match[3];
3826 endtoken = star_match[4];
3828 whitename = star_match[1];
3829 blackname = star_match[3];
3830 why = star_match[5];
3831 endtoken = star_match[6];
3834 /* Game start messages */
3835 if (strncmp(why, "Creating ", 9) == 0 ||
3836 strncmp(why, "Continuing ", 11) == 0) {
3837 gs_gamenum = gamenum;
3838 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3839 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3841 if (appData.zippyPlay) {
3842 ZippyGameStart(whitename, blackname);
3845 partnerBoardValid = FALSE; // [HGM] bughouse
3849 /* Game end messages */
3850 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3851 ics_gamenum != gamenum) {
3854 while (endtoken[0] == ' ') endtoken++;
3855 switch (endtoken[0]) {
3858 endtype = GameUnfinished;
3861 endtype = BlackWins;
3864 if (endtoken[1] == '/')
3865 endtype = GameIsDrawn;
3867 endtype = WhiteWins;
3870 GameEnds(endtype, why, GE_ICS);
3872 if (appData.zippyPlay && first.initDone) {
3873 ZippyGameEnd(endtype, why);
3874 if (first.pr == NULL) {
3875 /* Start the next process early so that we'll
3876 be ready for the next challenge */
3877 StartChessProgram(&first);
3879 /* Send "new" early, in case this command takes
3880 a long time to finish, so that we'll be ready
3881 for the next challenge. */
3882 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3886 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3890 if (looking_at(buf, &i, "Removing game * from observation") ||
3891 looking_at(buf, &i, "no longer observing game *") ||
3892 looking_at(buf, &i, "Game * (*) has no examiners")) {
3893 if (gameMode == IcsObserving &&
3894 atoi(star_match[0]) == ics_gamenum)
3896 /* icsEngineAnalyze */
3897 if (appData.icsEngineAnalyze) {
3904 ics_user_moved = FALSE;
3909 if (looking_at(buf, &i, "no longer examining game *")) {
3910 if (gameMode == IcsExamining &&
3911 atoi(star_match[0]) == ics_gamenum)
3915 ics_user_moved = FALSE;
3920 /* Advance leftover_start past any newlines we find,
3921 so only partial lines can get reparsed */
3922 if (looking_at(buf, &i, "\n")) {
3923 prevColor = curColor;
3924 if (curColor != ColorNormal) {
3925 if (oldi > next_out) {
3926 SendToPlayer(&buf[next_out], oldi - next_out);
3929 Colorize(ColorNormal, FALSE);
3930 curColor = ColorNormal;
3932 if (started == STARTED_BOARD) {
3933 started = STARTED_NONE;
3934 parse[parse_pos] = NULLCHAR;
3935 ParseBoard12(parse);
3938 /* Send premove here */
3939 if (appData.premove) {
3941 if (currentMove == 0 &&
3942 gameMode == IcsPlayingWhite &&
3943 appData.premoveWhite) {
3944 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3945 if (appData.debugMode)
3946 fprintf(debugFP, "Sending premove:\n");
3948 } else if (currentMove == 1 &&
3949 gameMode == IcsPlayingBlack &&
3950 appData.premoveBlack) {
3951 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3952 if (appData.debugMode)
3953 fprintf(debugFP, "Sending premove:\n");
3955 } else if (gotPremove) {
3957 ClearPremoveHighlights();
3958 if (appData.debugMode)
3959 fprintf(debugFP, "Sending premove:\n");
3960 UserMoveEvent(premoveFromX, premoveFromY,
3961 premoveToX, premoveToY,
3966 /* Usually suppress following prompt */
3967 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3968 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3969 if (looking_at(buf, &i, "*% ")) {
3970 savingComment = FALSE;
3975 } else if (started == STARTED_HOLDINGS) {
3977 char new_piece[MSG_SIZ];
3978 started = STARTED_NONE;
3979 parse[parse_pos] = NULLCHAR;
3980 if (appData.debugMode)
3981 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3982 parse, currentMove);
3983 if (sscanf(parse, " game %d", &gamenum) == 1) {
3984 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3985 if (gameInfo.variant == VariantNormal) {
3986 /* [HGM] We seem to switch variant during a game!
3987 * Presumably no holdings were displayed, so we have
3988 * to move the position two files to the right to
3989 * create room for them!
3991 VariantClass newVariant;
3992 switch(gameInfo.boardWidth) { // base guess on board width
3993 case 9: newVariant = VariantShogi; break;
3994 case 10: newVariant = VariantGreat; break;
3995 default: newVariant = VariantCrazyhouse; break;
3997 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3998 /* Get a move list just to see the header, which
3999 will tell us whether this is really bug or zh */
4000 if (ics_getting_history == H_FALSE) {
4001 ics_getting_history = H_REQUESTED;
4002 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006 new_piece[0] = NULLCHAR;
4007 sscanf(parse, "game %d white [%s black [%s <- %s",
4008 &gamenum, white_holding, black_holding,
4010 white_holding[strlen(white_holding)-1] = NULLCHAR;
4011 black_holding[strlen(black_holding)-1] = NULLCHAR;
4012 /* [HGM] copy holdings to board holdings area */
4013 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4014 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4015 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4017 if (appData.zippyPlay && first.initDone) {
4018 ZippyHoldings(white_holding, black_holding,
4022 if (tinyLayout || smallLayout) {
4023 char wh[16], bh[16];
4024 PackHolding(wh, white_holding);
4025 PackHolding(bh, black_holding);
4026 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4027 gameInfo.white, gameInfo.black);
4029 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4030 gameInfo.white, white_holding,
4031 gameInfo.black, black_holding);
4033 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4034 DrawPosition(FALSE, boards[currentMove]);
4036 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4037 sscanf(parse, "game %d white [%s black [%s <- %s",
4038 &gamenum, white_holding, black_holding,
4040 white_holding[strlen(white_holding)-1] = NULLCHAR;
4041 black_holding[strlen(black_holding)-1] = NULLCHAR;
4042 /* [HGM] copy holdings to partner-board holdings area */
4043 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4044 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4045 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4046 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4047 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4050 /* Suppress following prompt */
4051 if (looking_at(buf, &i, "*% ")) {
4052 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4053 savingComment = FALSE;
4061 i++; /* skip unparsed character and loop back */
4064 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4065 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4066 // SendToPlayer(&buf[next_out], i - next_out);
4067 started != STARTED_HOLDINGS && leftover_start > next_out) {
4068 SendToPlayer(&buf[next_out], leftover_start - next_out);
4072 leftover_len = buf_len - leftover_start;
4073 /* if buffer ends with something we couldn't parse,
4074 reparse it after appending the next read */
4076 } else if (count == 0) {
4077 RemoveInputSource(isr);
4078 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4080 DisplayFatalError(_("Error reading from ICS"), error, 1);
4085 /* Board style 12 looks like this:
4087 <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
4089 * The "<12> " is stripped before it gets to this routine. The two
4090 * trailing 0's (flip state and clock ticking) are later addition, and
4091 * some chess servers may not have them, or may have only the first.
4092 * Additional trailing fields may be added in the future.
4095 #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"
4097 #define RELATION_OBSERVING_PLAYED 0
4098 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4099 #define RELATION_PLAYING_MYMOVE 1
4100 #define RELATION_PLAYING_NOTMYMOVE -1
4101 #define RELATION_EXAMINING 2
4102 #define RELATION_ISOLATED_BOARD -3
4103 #define RELATION_STARTING_POSITION -4 /* FICS only */
4106 ParseBoard12(string)
4109 GameMode newGameMode;
4110 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4111 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4112 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4113 char to_play, board_chars[200];
4114 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4115 char black[32], white[32];
4117 int prevMove = currentMove;
4120 int fromX, fromY, toX, toY;
4122 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4123 char *bookHit = NULL; // [HGM] book
4124 Boolean weird = FALSE, reqFlag = FALSE;
4126 fromX = fromY = toX = toY = -1;
4130 if (appData.debugMode)
4131 fprintf(debugFP, _("Parsing board: %s\n"), string);
4133 move_str[0] = NULLCHAR;
4134 elapsed_time[0] = NULLCHAR;
4135 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4137 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4138 if(string[i] == ' ') { ranks++; files = 0; }
4140 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4143 for(j = 0; j <i; j++) board_chars[j] = string[j];
4144 board_chars[i] = '\0';
4147 n = sscanf(string, PATTERN, &to_play, &double_push,
4148 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4149 &gamenum, white, black, &relation, &basetime, &increment,
4150 &white_stren, &black_stren, &white_time, &black_time,
4151 &moveNum, str, elapsed_time, move_str, &ics_flip,
4155 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4156 DisplayError(str, 0);
4160 /* Convert the move number to internal form */
4161 moveNum = (moveNum - 1) * 2;
4162 if (to_play == 'B') moveNum++;
4163 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4164 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4170 case RELATION_OBSERVING_PLAYED:
4171 case RELATION_OBSERVING_STATIC:
4172 if (gamenum == -1) {
4173 /* Old ICC buglet */
4174 relation = RELATION_OBSERVING_STATIC;
4176 newGameMode = IcsObserving;
4178 case RELATION_PLAYING_MYMOVE:
4179 case RELATION_PLAYING_NOTMYMOVE:
4181 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4182 IcsPlayingWhite : IcsPlayingBlack;
4184 case RELATION_EXAMINING:
4185 newGameMode = IcsExamining;
4187 case RELATION_ISOLATED_BOARD:
4189 /* Just display this board. If user was doing something else,
4190 we will forget about it until the next board comes. */
4191 newGameMode = IcsIdle;
4193 case RELATION_STARTING_POSITION:
4194 newGameMode = gameMode;
4198 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4199 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4200 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4202 for (k = 0; k < ranks; k++) {
4203 for (j = 0; j < files; j++)
4204 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4205 if(gameInfo.holdingsWidth > 1) {
4206 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4207 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4210 CopyBoard(partnerBoard, board);
4211 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4212 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4213 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4214 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4215 if(toSqr = strchr(str, '-')) {
4216 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4217 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4218 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4219 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4220 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4221 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4222 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4223 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4224 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4225 DisplayMessage(partnerStatus, "");
4226 partnerBoardValid = TRUE;
4230 /* Modify behavior for initial board display on move listing
4233 switch (ics_getting_history) {
4237 case H_GOT_REQ_HEADER:
4238 case H_GOT_UNREQ_HEADER:
4239 /* This is the initial position of the current game */
4240 gamenum = ics_gamenum;
4241 moveNum = 0; /* old ICS bug workaround */
4242 if (to_play == 'B') {
4243 startedFromSetupPosition = TRUE;
4244 blackPlaysFirst = TRUE;
4246 if (forwardMostMove == 0) forwardMostMove = 1;
4247 if (backwardMostMove == 0) backwardMostMove = 1;
4248 if (currentMove == 0) currentMove = 1;
4250 newGameMode = gameMode;
4251 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4253 case H_GOT_UNWANTED_HEADER:
4254 /* This is an initial board that we don't want */
4256 case H_GETTING_MOVES:
4257 /* Should not happen */
4258 DisplayError(_("Error gathering move list: extra board"), 0);
4259 ics_getting_history = H_FALSE;
4263 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4264 weird && (int)gameInfo.variant < (int)VariantShogi) {
4265 /* [HGM] We seem to have switched variant unexpectedly
4266 * Try to guess new variant from board size
4268 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4269 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4270 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4271 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4272 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4273 if(!weird) newVariant = VariantNormal;
4274 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4275 /* Get a move list just to see the header, which
4276 will tell us whether this is really bug or zh */
4277 if (ics_getting_history == H_FALSE) {
4278 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4279 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4284 /* Take action if this is the first board of a new game, or of a
4285 different game than is currently being displayed. */
4286 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4287 relation == RELATION_ISOLATED_BOARD) {
4289 /* Forget the old game and get the history (if any) of the new one */
4290 if (gameMode != BeginningOfGame) {
4294 if (appData.autoRaiseBoard) BoardToTop();
4296 if (gamenum == -1) {
4297 newGameMode = IcsIdle;
4298 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4299 appData.getMoveList && !reqFlag) {
4300 /* Need to get game history */
4301 ics_getting_history = H_REQUESTED;
4302 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4306 /* Initially flip the board to have black on the bottom if playing
4307 black or if the ICS flip flag is set, but let the user change
4308 it with the Flip View button. */
4309 flipView = appData.autoFlipView ?
4310 (newGameMode == IcsPlayingBlack) || ics_flip :
4313 /* Done with values from previous mode; copy in new ones */
4314 gameMode = newGameMode;
4316 ics_gamenum = gamenum;
4317 if (gamenum == gs_gamenum) {
4318 int klen = strlen(gs_kind);
4319 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4320 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4321 gameInfo.event = StrSave(str);
4323 gameInfo.event = StrSave("ICS game");
4325 gameInfo.site = StrSave(appData.icsHost);
4326 gameInfo.date = PGNDate();
4327 gameInfo.round = StrSave("-");
4328 gameInfo.white = StrSave(white);
4329 gameInfo.black = StrSave(black);
4330 timeControl = basetime * 60 * 1000;
4332 timeIncrement = increment * 1000;
4333 movesPerSession = 0;
4334 gameInfo.timeControl = TimeControlTagValue();
4335 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4336 if (appData.debugMode) {
4337 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4338 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4339 setbuf(debugFP, NULL);
4342 gameInfo.outOfBook = NULL;
4344 /* Do we have the ratings? */
4345 if (strcmp(player1Name, white) == 0 &&
4346 strcmp(player2Name, black) == 0) {
4347 if (appData.debugMode)
4348 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4349 player1Rating, player2Rating);
4350 gameInfo.whiteRating = player1Rating;
4351 gameInfo.blackRating = player2Rating;
4352 } else if (strcmp(player2Name, white) == 0 &&
4353 strcmp(player1Name, black) == 0) {
4354 if (appData.debugMode)
4355 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356 player2Rating, player1Rating);
4357 gameInfo.whiteRating = player2Rating;
4358 gameInfo.blackRating = player1Rating;
4360 player1Name[0] = player2Name[0] = NULLCHAR;
4362 /* Silence shouts if requested */
4363 if (appData.quietPlay &&
4364 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4365 SendToICS(ics_prefix);
4366 SendToICS("set shout 0\n");
4370 /* Deal with midgame name changes */
4372 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4373 if (gameInfo.white) free(gameInfo.white);
4374 gameInfo.white = StrSave(white);
4376 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4377 if (gameInfo.black) free(gameInfo.black);
4378 gameInfo.black = StrSave(black);
4382 /* Throw away game result if anything actually changes in examine mode */
4383 if (gameMode == IcsExamining && !newGame) {
4384 gameInfo.result = GameUnfinished;
4385 if (gameInfo.resultDetails != NULL) {
4386 free(gameInfo.resultDetails);
4387 gameInfo.resultDetails = NULL;
4391 /* In pausing && IcsExamining mode, we ignore boards coming
4392 in if they are in a different variation than we are. */
4393 if (pauseExamInvalid) return;
4394 if (pausing && gameMode == IcsExamining) {
4395 if (moveNum <= pauseExamForwardMostMove) {
4396 pauseExamInvalid = TRUE;
4397 forwardMostMove = pauseExamForwardMostMove;
4402 if (appData.debugMode) {
4403 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4405 /* Parse the board */
4406 for (k = 0; k < ranks; k++) {
4407 for (j = 0; j < files; j++)
4408 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4409 if(gameInfo.holdingsWidth > 1) {
4410 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4411 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4414 CopyBoard(boards[moveNum], board);
4415 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4417 startedFromSetupPosition =
4418 !CompareBoards(board, initialPosition);
4419 if(startedFromSetupPosition)
4420 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4423 /* [HGM] Set castling rights. Take the outermost Rooks,
4424 to make it also work for FRC opening positions. Note that board12
4425 is really defective for later FRC positions, as it has no way to
4426 indicate which Rook can castle if they are on the same side of King.
4427 For the initial position we grant rights to the outermost Rooks,
4428 and remember thos rights, and we then copy them on positions
4429 later in an FRC game. This means WB might not recognize castlings with
4430 Rooks that have moved back to their original position as illegal,
4431 but in ICS mode that is not its job anyway.
4433 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4434 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4436 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4437 if(board[0][i] == WhiteRook) j = i;
4438 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4439 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4440 if(board[0][i] == WhiteRook) j = i;
4441 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4443 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4444 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4446 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4450 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4451 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4452 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4453 if(board[BOARD_HEIGHT-1][k] == bKing)
4454 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4455 if(gameInfo.variant == VariantTwoKings) {
4456 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4457 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4458 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4461 r = boards[moveNum][CASTLING][0] = initialRights[0];
4462 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4463 r = boards[moveNum][CASTLING][1] = initialRights[1];
4464 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4465 r = boards[moveNum][CASTLING][3] = initialRights[3];
4466 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4467 r = boards[moveNum][CASTLING][4] = initialRights[4];
4468 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4469 /* wildcastle kludge: always assume King has rights */
4470 r = boards[moveNum][CASTLING][2] = initialRights[2];
4471 r = boards[moveNum][CASTLING][5] = initialRights[5];
4473 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4474 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4477 if (ics_getting_history == H_GOT_REQ_HEADER ||
4478 ics_getting_history == H_GOT_UNREQ_HEADER) {
4479 /* This was an initial position from a move list, not
4480 the current position */
4484 /* Update currentMove and known move number limits */
4485 newMove = newGame || moveNum > forwardMostMove;
4488 forwardMostMove = backwardMostMove = currentMove = moveNum;
4489 if (gameMode == IcsExamining && moveNum == 0) {
4490 /* Workaround for ICS limitation: we are not told the wild
4491 type when starting to examine a game. But if we ask for
4492 the move list, the move list header will tell us */
4493 ics_getting_history = H_REQUESTED;
4494 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4497 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4498 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4500 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4501 /* [HGM] applied this also to an engine that is silently watching */
4502 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4503 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4504 gameInfo.variant == currentlyInitializedVariant) {
4505 takeback = forwardMostMove - moveNum;
4506 for (i = 0; i < takeback; i++) {
4507 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4508 SendToProgram("undo\n", &first);
4513 forwardMostMove = moveNum;
4514 if (!pausing || currentMove > forwardMostMove)
4515 currentMove = forwardMostMove;
4517 /* New part of history that is not contiguous with old part */
4518 if (pausing && gameMode == IcsExamining) {
4519 pauseExamInvalid = TRUE;
4520 forwardMostMove = pauseExamForwardMostMove;
4523 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4525 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4526 // [HGM] when we will receive the move list we now request, it will be
4527 // fed to the engine from the first move on. So if the engine is not
4528 // in the initial position now, bring it there.
4529 InitChessProgram(&first, 0);
4532 ics_getting_history = H_REQUESTED;
4533 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4536 forwardMostMove = backwardMostMove = currentMove = moveNum;
4539 /* Update the clocks */
4540 if (strchr(elapsed_time, '.')) {
4542 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4543 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4545 /* Time is in seconds */
4546 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4547 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4552 if (appData.zippyPlay && newGame &&
4553 gameMode != IcsObserving && gameMode != IcsIdle &&
4554 gameMode != IcsExamining)
4555 ZippyFirstBoard(moveNum, basetime, increment);
4558 /* Put the move on the move list, first converting
4559 to canonical algebraic form. */
4561 if (appData.debugMode) {
4562 if (appData.debugMode) { int f = forwardMostMove;
4563 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4564 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4565 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4567 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4568 fprintf(debugFP, "moveNum = %d\n", moveNum);
4569 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4570 setbuf(debugFP, NULL);
4572 if (moveNum <= backwardMostMove) {
4573 /* We don't know what the board looked like before
4575 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4576 strcat(parseList[moveNum - 1], " ");
4577 strcat(parseList[moveNum - 1], elapsed_time);
4578 moveList[moveNum - 1][0] = NULLCHAR;
4579 } else if (strcmp(move_str, "none") == 0) {
4580 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4581 /* Again, we don't know what the board looked like;
4582 this is really the start of the game. */
4583 parseList[moveNum - 1][0] = NULLCHAR;
4584 moveList[moveNum - 1][0] = NULLCHAR;
4585 backwardMostMove = moveNum;
4586 startedFromSetupPosition = TRUE;
4587 fromX = fromY = toX = toY = -1;
4589 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4590 // So we parse the long-algebraic move string in stead of the SAN move
4591 int valid; char buf[MSG_SIZ], *prom;
4593 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4594 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4595 // str looks something like "Q/a1-a2"; kill the slash
4597 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4598 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4599 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4600 strcat(buf, prom); // long move lacks promo specification!
4601 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4602 if(appData.debugMode)
4603 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4604 safeStrCpy(move_str, buf, MSG_SIZ);
4606 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4607 &fromX, &fromY, &toX, &toY, &promoChar)
4608 || ParseOneMove(buf, moveNum - 1, &moveType,
4609 &fromX, &fromY, &toX, &toY, &promoChar);
4610 // end of long SAN patch
4612 (void) CoordsToAlgebraic(boards[moveNum - 1],
4613 PosFlags(moveNum - 1),
4614 fromY, fromX, toY, toX, promoChar,
4615 parseList[moveNum-1]);
4616 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4622 if(gameInfo.variant != VariantShogi)
4623 strcat(parseList[moveNum - 1], "+");
4626 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4627 strcat(parseList[moveNum - 1], "#");
4630 strcat(parseList[moveNum - 1], " ");
4631 strcat(parseList[moveNum - 1], elapsed_time);
4632 /* currentMoveString is set as a side-effect of ParseOneMove */
4633 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4634 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4635 strcat(moveList[moveNum - 1], "\n");
4637 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4638 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4639 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4640 ChessSquare old, new = boards[moveNum][k][j];
4641 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4642 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4643 if(old == new) continue;
4644 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4645 else if(new == WhiteWazir || new == BlackWazir) {
4646 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4647 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4648 else boards[moveNum][k][j] = old; // preserve type of Gold
4649 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4650 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4653 /* Move from ICS was illegal!? Punt. */
4654 if (appData.debugMode) {
4655 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4656 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4658 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659 strcat(parseList[moveNum - 1], " ");
4660 strcat(parseList[moveNum - 1], elapsed_time);
4661 moveList[moveNum - 1][0] = NULLCHAR;
4662 fromX = fromY = toX = toY = -1;
4665 if (appData.debugMode) {
4666 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4667 setbuf(debugFP, NULL);
4671 /* Send move to chess program (BEFORE animating it). */
4672 if (appData.zippyPlay && !newGame && newMove &&
4673 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4675 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4676 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4677 if (moveList[moveNum - 1][0] == NULLCHAR) {
4678 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4680 DisplayError(str, 0);
4682 if (first.sendTime) {
4683 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4685 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4686 if (firstMove && !bookHit) {
4688 if (first.useColors) {
4689 SendToProgram(gameMode == IcsPlayingWhite ?
4691 "black\ngo\n", &first);
4693 SendToProgram("go\n", &first);
4695 first.maybeThinking = TRUE;
4698 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4699 if (moveList[moveNum - 1][0] == NULLCHAR) {
4700 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4701 DisplayError(str, 0);
4703 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4704 SendMoveToProgram(moveNum - 1, &first);
4711 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4712 /* If move comes from a remote source, animate it. If it
4713 isn't remote, it will have already been animated. */
4714 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4715 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4717 if (!pausing && appData.highlightLastMove) {
4718 SetHighlights(fromX, fromY, toX, toY);
4722 /* Start the clocks */
4723 whiteFlag = blackFlag = FALSE;
4724 appData.clockMode = !(basetime == 0 && increment == 0);
4726 ics_clock_paused = TRUE;
4728 } else if (ticking == 1) {
4729 ics_clock_paused = FALSE;
4731 if (gameMode == IcsIdle ||
4732 relation == RELATION_OBSERVING_STATIC ||
4733 relation == RELATION_EXAMINING ||
4735 DisplayBothClocks();
4739 /* Display opponents and material strengths */
4740 if (gameInfo.variant != VariantBughouse &&
4741 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4742 if (tinyLayout || smallLayout) {
4743 if(gameInfo.variant == VariantNormal)
4744 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4745 gameInfo.white, white_stren, gameInfo.black, black_stren,
4746 basetime, increment);
4748 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4749 gameInfo.white, white_stren, gameInfo.black, black_stren,
4750 basetime, increment, (int) gameInfo.variant);
4752 if(gameInfo.variant == VariantNormal)
4753 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4754 gameInfo.white, white_stren, gameInfo.black, black_stren,
4755 basetime, increment);
4757 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4758 gameInfo.white, white_stren, gameInfo.black, black_stren,
4759 basetime, increment, VariantName(gameInfo.variant));
4762 if (appData.debugMode) {
4763 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4768 /* Display the board */
4769 if (!pausing && !appData.noGUI) {
4771 if (appData.premove)
4773 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4774 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4775 ClearPremoveHighlights();
4777 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4778 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4779 DrawPosition(j, boards[currentMove]);
4781 DisplayMove(moveNum - 1);
4782 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4783 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4784 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4785 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4789 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4791 if(bookHit) { // [HGM] book: simulate book reply
4792 static char bookMove[MSG_SIZ]; // a bit generous?
4794 programStats.nodes = programStats.depth = programStats.time =
4795 programStats.score = programStats.got_only_move = 0;
4796 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4798 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4799 strcat(bookMove, bookHit);
4800 HandleMachineMove(bookMove, &first);
4809 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4810 ics_getting_history = H_REQUESTED;
4811 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4817 AnalysisPeriodicEvent(force)
4820 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4821 && !force) || !appData.periodicUpdates)
4824 /* Send . command to Crafty to collect stats */
4825 SendToProgram(".\n", &first);
4827 /* Don't send another until we get a response (this makes
4828 us stop sending to old Crafty's which don't understand
4829 the "." command (sending illegal cmds resets node count & time,
4830 which looks bad)) */
4831 programStats.ok_to_send = 0;
4834 void ics_update_width(new_width)
4837 ics_printf("set width %d\n", new_width);
4841 SendMoveToProgram(moveNum, cps)
4843 ChessProgramState *cps;
4847 if (cps->useUsermove) {
4848 SendToProgram("usermove ", cps);
4852 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4853 int len = space - parseList[moveNum];
4854 memcpy(buf, parseList[moveNum], len);
4856 buf[len] = NULLCHAR;
4858 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4860 SendToProgram(buf, cps);
4862 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4863 AlphaRank(moveList[moveNum], 4);
4864 SendToProgram(moveList[moveNum], cps);
4865 AlphaRank(moveList[moveNum], 4); // and back
4867 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4868 * the engine. It would be nice to have a better way to identify castle
4870 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4871 && cps->useOOCastle) {
4872 int fromX = moveList[moveNum][0] - AAA;
4873 int fromY = moveList[moveNum][1] - ONE;
4874 int toX = moveList[moveNum][2] - AAA;
4875 int toY = moveList[moveNum][3] - ONE;
4876 if((boards[moveNum][fromY][fromX] == WhiteKing
4877 && boards[moveNum][toY][toX] == WhiteRook)
4878 || (boards[moveNum][fromY][fromX] == BlackKing
4879 && boards[moveNum][toY][toX] == BlackRook)) {
4880 if(toX > fromX) SendToProgram("O-O\n", cps);
4881 else SendToProgram("O-O-O\n", cps);
4883 else SendToProgram(moveList[moveNum], cps);
4885 else SendToProgram(moveList[moveNum], cps);
4886 /* End of additions by Tord */
4889 /* [HGM] setting up the opening has brought engine in force mode! */
4890 /* Send 'go' if we are in a mode where machine should play. */
4891 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4892 (gameMode == TwoMachinesPlay ||
4894 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4896 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4897 SendToProgram("go\n", cps);
4898 if (appData.debugMode) {
4899 fprintf(debugFP, "(extra)\n");
4902 setboardSpoiledMachineBlack = 0;
4906 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4908 int fromX, fromY, toX, toY;
4911 char user_move[MSG_SIZ];
4915 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4916 (int)moveType, fromX, fromY, toX, toY);
4917 DisplayError(user_move + strlen("say "), 0);
4919 case WhiteKingSideCastle:
4920 case BlackKingSideCastle:
4921 case WhiteQueenSideCastleWild:
4922 case BlackQueenSideCastleWild:
4924 case WhiteHSideCastleFR:
4925 case BlackHSideCastleFR:
4927 snprintf(user_move, MSG_SIZ, "o-o\n");
4929 case WhiteQueenSideCastle:
4930 case BlackQueenSideCastle:
4931 case WhiteKingSideCastleWild:
4932 case BlackKingSideCastleWild:
4934 case WhiteASideCastleFR:
4935 case BlackASideCastleFR:
4937 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4939 case WhiteNonPromotion:
4940 case BlackNonPromotion:
4941 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4943 case WhitePromotion:
4944 case BlackPromotion:
4945 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4946 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4947 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4948 PieceToChar(WhiteFerz));
4949 else if(gameInfo.variant == VariantGreat)
4950 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4951 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4952 PieceToChar(WhiteMan));
4954 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4955 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4961 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4962 ToUpper(PieceToChar((ChessSquare) fromX)),
4963 AAA + toX, ONE + toY);
4965 case IllegalMove: /* could be a variant we don't quite understand */
4966 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4968 case WhiteCapturesEnPassant:
4969 case BlackCapturesEnPassant:
4970 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4971 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4974 SendToICS(user_move);
4975 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4976 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4981 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4982 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4983 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4984 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4985 DisplayError("You cannot do this while you are playing or observing", 0);
4988 if(gameMode != IcsExamining) { // is this ever not the case?
4989 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4991 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4992 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4993 } else { // on FICS we must first go to general examine mode
4994 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4996 if(gameInfo.variant != VariantNormal) {
4997 // try figure out wild number, as xboard names are not always valid on ICS
4998 for(i=1; i<=36; i++) {
4999 snprintf(buf, MSG_SIZ, "wild/%d", i);
5000 if(StringToVariant(buf) == gameInfo.variant) break;
5002 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5003 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5004 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5005 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5006 SendToICS(ics_prefix);
5008 if(startedFromSetupPosition || backwardMostMove != 0) {
5009 fen = PositionToFEN(backwardMostMove, NULL);
5010 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5011 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5013 } else { // FICS: everything has to set by separate bsetup commands
5014 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5015 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5017 if(!WhiteOnMove(backwardMostMove)) {
5018 SendToICS("bsetup tomove black\n");
5020 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5021 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5023 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5024 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5026 i = boards[backwardMostMove][EP_STATUS];
5027 if(i >= 0) { // set e.p.
5028 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5034 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5035 SendToICS("bsetup done\n"); // switch to normal examining.
5037 for(i = backwardMostMove; i<last; i++) {
5039 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5042 SendToICS(ics_prefix);
5043 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5047 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5052 if (rf == DROP_RANK) {
5053 sprintf(move, "%c@%c%c\n",
5054 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5056 if (promoChar == 'x' || promoChar == NULLCHAR) {
5057 sprintf(move, "%c%c%c%c\n",
5058 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5060 sprintf(move, "%c%c%c%c%c\n",
5061 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5067 ProcessICSInitScript(f)
5072 while (fgets(buf, MSG_SIZ, f)) {
5073 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5080 static int lastX, lastY, selectFlag, dragging;
5085 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5086 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5087 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5088 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5089 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5090 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5093 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5094 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5095 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5096 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5098 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5099 appData.testLegality && (promoSweep == king ||
5100 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5101 ChangeDragPiece(promoSweep);
5104 int PromoScroll(int x, int y)
5108 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5109 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5110 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5111 if(!step) return FALSE;
5112 lastX = x; lastY = y;
5113 if((promoSweep < BlackPawn) == flipView) step = -step;
5114 if(step > 0) selectFlag = 1;
5115 if(!selectFlag) Sweep(step);
5122 ChessSquare piece = boards[currentMove][toY][toX];
5125 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5126 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5127 if(!step) step = -1;
5128 } while(PieceToChar(pieceSweep) == '.');
5129 boards[currentMove][toY][toX] = pieceSweep;
5130 DrawPosition(FALSE, boards[currentMove]);
5131 boards[currentMove][toY][toX] = piece;
5133 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5135 AlphaRank(char *move, int n)
5137 // char *p = move, c; int x, y;
5139 if (appData.debugMode) {
5140 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5144 move[2]>='0' && move[2]<='9' &&
5145 move[3]>='a' && move[3]<='x' ) {
5147 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5148 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5150 if(move[0]>='0' && move[0]<='9' &&
5151 move[1]>='a' && move[1]<='x' &&
5152 move[2]>='0' && move[2]<='9' &&
5153 move[3]>='a' && move[3]<='x' ) {
5154 /* input move, Shogi -> normal */
5155 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5156 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5157 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5158 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5161 move[3]>='0' && move[3]<='9' &&
5162 move[2]>='a' && move[2]<='x' ) {
5164 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5165 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5168 move[0]>='a' && move[0]<='x' &&
5169 move[3]>='0' && move[3]<='9' &&
5170 move[2]>='a' && move[2]<='x' ) {
5171 /* output move, normal -> Shogi */
5172 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5173 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5174 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5175 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5176 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5178 if (appData.debugMode) {
5179 fprintf(debugFP, " out = '%s'\n", move);
5183 char yy_textstr[8000];
5185 /* Parser for moves from gnuchess, ICS, or user typein box */
5187 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5190 ChessMove *moveType;
5191 int *fromX, *fromY, *toX, *toY;
5194 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5196 switch (*moveType) {
5197 case WhitePromotion:
5198 case BlackPromotion:
5199 case WhiteNonPromotion:
5200 case BlackNonPromotion:
5202 case WhiteCapturesEnPassant:
5203 case BlackCapturesEnPassant:
5204 case WhiteKingSideCastle:
5205 case WhiteQueenSideCastle:
5206 case BlackKingSideCastle:
5207 case BlackQueenSideCastle:
5208 case WhiteKingSideCastleWild:
5209 case WhiteQueenSideCastleWild:
5210 case BlackKingSideCastleWild:
5211 case BlackQueenSideCastleWild:
5212 /* Code added by Tord: */
5213 case WhiteHSideCastleFR:
5214 case WhiteASideCastleFR:
5215 case BlackHSideCastleFR:
5216 case BlackASideCastleFR:
5217 /* End of code added by Tord */
5218 case IllegalMove: /* bug or odd chess variant */
5219 *fromX = currentMoveString[0] - AAA;
5220 *fromY = currentMoveString[1] - ONE;
5221 *toX = currentMoveString[2] - AAA;
5222 *toY = currentMoveString[3] - ONE;
5223 *promoChar = currentMoveString[4];
5224 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5225 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5226 if (appData.debugMode) {
5227 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5229 *fromX = *fromY = *toX = *toY = 0;
5232 if (appData.testLegality) {
5233 return (*moveType != IllegalMove);
5235 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5236 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5241 *fromX = *moveType == WhiteDrop ?
5242 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5243 (int) CharToPiece(ToLower(currentMoveString[0]));
5245 *toX = currentMoveString[2] - AAA;
5246 *toY = currentMoveString[3] - ONE;
5247 *promoChar = NULLCHAR;
5251 case ImpossibleMove:
5261 if (appData.debugMode) {
5262 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5265 *fromX = *fromY = *toX = *toY = 0;
5266 *promoChar = NULLCHAR;
5271 Boolean pushed = FALSE;
5274 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5275 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5276 int fromX, fromY, toX, toY; char promoChar;
5281 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5282 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5285 endPV = forwardMostMove;
5287 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5288 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5289 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5290 if(appData.debugMode){
5291 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);
5293 if(!valid && nr == 0 &&
5294 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5295 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5296 // Hande case where played move is different from leading PV move
5297 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5298 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5299 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5300 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5301 endPV += 2; // if position different, keep this
5302 moveList[endPV-1][0] = fromX + AAA;
5303 moveList[endPV-1][1] = fromY + ONE;
5304 moveList[endPV-1][2] = toX + AAA;
5305 moveList[endPV-1][3] = toY + ONE;
5306 parseList[endPV-1][0] = NULLCHAR;
5307 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5310 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5311 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5312 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5313 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5314 valid++; // allow comments in PV
5318 if(endPV+1 > framePtr) break; // no space, truncate
5321 CopyBoard(boards[endPV], boards[endPV-1]);
5322 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5323 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5324 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5325 CoordsToAlgebraic(boards[endPV - 1],
5326 PosFlags(endPV - 1),
5327 fromY, fromX, toY, toX, promoChar,
5328 parseList[endPV - 1]);
5330 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5331 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5332 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5333 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5334 DrawPosition(TRUE, boards[currentMove]);
5338 MultiPV(ChessProgramState *cps)
5339 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5341 for(i=0; i<cps->nrOptions; i++)
5342 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5348 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5350 int startPV, multi, lineStart, origIndex = index;
5351 char *p, buf2[MSG_SIZ];
5353 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5354 lastX = x; lastY = y;
5355 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5356 lineStart = startPV = index;
5357 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5358 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5360 do{ while(buf[index] && buf[index] != '\n') index++;
5361 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5363 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5364 int n = first.option[multi].value;
5365 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5366 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5367 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5368 first.option[multi].value = n;
5372 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5373 *start = startPV; *end = index-1;
5378 LoadPV(int x, int y)
5379 { // called on right mouse click to load PV
5380 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5381 lastX = x; lastY = y;
5382 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5389 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5390 if(endPV < 0) return;
5392 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5393 Boolean saveAnimate = appData.animate;
5395 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5396 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5397 } else storedGames--; // abandon shelved tail of original game
5400 forwardMostMove = currentMove;
5401 currentMove = oldFMM;
5402 appData.animate = FALSE;
5403 ToNrEvent(forwardMostMove);
5404 appData.animate = saveAnimate;
5406 currentMove = forwardMostMove;
5407 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5408 ClearPremoveHighlights();
5409 DrawPosition(TRUE, boards[currentMove]);
5413 MovePV(int x, int y, int h)
5414 { // step through PV based on mouse coordinates (called on mouse move)
5415 int margin = h>>3, step = 0;
5417 // we must somehow check if right button is still down (might be released off board!)
5418 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5419 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5420 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5422 lastX = x; lastY = y;
5424 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5425 if(endPV < 0) return;
5426 if(y < margin) step = 1; else
5427 if(y > h - margin) step = -1;
5428 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5429 currentMove += step;
5430 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5431 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5432 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5433 DrawPosition(FALSE, boards[currentMove]);
5437 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5438 // All positions will have equal probability, but the current method will not provide a unique
5439 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5445 int piecesLeft[(int)BlackPawn];
5446 int seed, nrOfShuffles;
5448 void GetPositionNumber()
5449 { // sets global variable seed
5452 seed = appData.defaultFrcPosition;
5453 if(seed < 0) { // randomize based on time for negative FRC position numbers
5454 for(i=0; i<50; i++) seed += random();
5455 seed = random() ^ random() >> 8 ^ random() << 8;
5456 if(seed<0) seed = -seed;
5460 int put(Board board, int pieceType, int rank, int n, int shade)
5461 // put the piece on the (n-1)-th empty squares of the given shade
5465 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5466 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5467 board[rank][i] = (ChessSquare) pieceType;
5468 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5470 piecesLeft[pieceType]--;
5478 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5479 // calculate where the next piece goes, (any empty square), and put it there
5483 i = seed % squaresLeft[shade];
5484 nrOfShuffles *= squaresLeft[shade];
5485 seed /= squaresLeft[shade];
5486 put(board, pieceType, rank, i, shade);
5489 void AddTwoPieces(Board board, int pieceType, int rank)
5490 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5492 int i, n=squaresLeft[ANY], j=n-1, k;
5494 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5495 i = seed % k; // pick one
5498 while(i >= j) i -= j--;
5499 j = n - 1 - j; i += j;
5500 put(board, pieceType, rank, j, ANY);
5501 put(board, pieceType, rank, i, ANY);
5504 void SetUpShuffle(Board board, int number)
5508 GetPositionNumber(); nrOfShuffles = 1;
5510 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5511 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5512 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5514 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5516 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5517 p = (int) board[0][i];
5518 if(p < (int) BlackPawn) piecesLeft[p] ++;
5519 board[0][i] = EmptySquare;
5522 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5523 // shuffles restricted to allow normal castling put KRR first
5524 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5525 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5526 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5527 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5528 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5529 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5530 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5531 put(board, WhiteRook, 0, 0, ANY);
5532 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5535 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5536 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5537 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5538 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5539 while(piecesLeft[p] >= 2) {
5540 AddOnePiece(board, p, 0, LITE);
5541 AddOnePiece(board, p, 0, DARK);
5543 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5546 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5547 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5548 // but we leave King and Rooks for last, to possibly obey FRC restriction
5549 if(p == (int)WhiteRook) continue;
5550 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5551 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5554 // now everything is placed, except perhaps King (Unicorn) and Rooks
5556 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5557 // Last King gets castling rights
5558 while(piecesLeft[(int)WhiteUnicorn]) {
5559 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5560 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5563 while(piecesLeft[(int)WhiteKing]) {
5564 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5570 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5571 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5574 // Only Rooks can be left; simply place them all
5575 while(piecesLeft[(int)WhiteRook]) {
5576 i = put(board, WhiteRook, 0, 0, ANY);
5577 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5580 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5582 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5585 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5586 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5589 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5592 int SetCharTable( char *table, const char * map )
5593 /* [HGM] moved here from winboard.c because of its general usefulness */
5594 /* Basically a safe strcpy that uses the last character as King */
5596 int result = FALSE; int NrPieces;
5598 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5599 && NrPieces >= 12 && !(NrPieces&1)) {
5600 int i; /* [HGM] Accept even length from 12 to 34 */
5602 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5603 for( i=0; i<NrPieces/2-1; i++ ) {
5605 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5607 table[(int) WhiteKing] = map[NrPieces/2-1];
5608 table[(int) BlackKing] = map[NrPieces-1];
5616 void Prelude(Board board)
5617 { // [HGM] superchess: random selection of exo-pieces
5618 int i, j, k; ChessSquare p;
5619 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5621 GetPositionNumber(); // use FRC position number
5623 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5624 SetCharTable(pieceToChar, appData.pieceToCharTable);
5625 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5626 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5629 j = seed%4; seed /= 4;
5630 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5631 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5632 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5633 j = seed%3 + (seed%3 >= j); seed /= 3;
5634 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5635 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5636 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5637 j = seed%3; seed /= 3;
5638 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5639 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5640 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5641 j = seed%2 + (seed%2 >= j); seed /= 2;
5642 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5643 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5644 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5645 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5646 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5647 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5648 put(board, exoPieces[0], 0, 0, ANY);
5649 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5653 InitPosition(redraw)
5656 ChessSquare (* pieces)[BOARD_FILES];
5657 int i, j, pawnRow, overrule,
5658 oldx = gameInfo.boardWidth,
5659 oldy = gameInfo.boardHeight,
5660 oldh = gameInfo.holdingsWidth;
5663 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5665 /* [AS] Initialize pv info list [HGM] and game status */
5667 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5668 pvInfoList[i].depth = 0;
5669 boards[i][EP_STATUS] = EP_NONE;
5670 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5673 initialRulePlies = 0; /* 50-move counter start */
5675 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5676 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5680 /* [HGM] logic here is completely changed. In stead of full positions */
5681 /* the initialized data only consist of the two backranks. The switch */
5682 /* selects which one we will use, which is than copied to the Board */
5683 /* initialPosition, which for the rest is initialized by Pawns and */
5684 /* empty squares. This initial position is then copied to boards[0], */
5685 /* possibly after shuffling, so that it remains available. */
5687 gameInfo.holdingsWidth = 0; /* default board sizes */
5688 gameInfo.boardWidth = 8;
5689 gameInfo.boardHeight = 8;
5690 gameInfo.holdingsSize = 0;
5691 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5692 for(i=0; i<BOARD_FILES-2; i++)
5693 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5694 initialPosition[EP_STATUS] = EP_NONE;
5695 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5696 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5697 SetCharTable(pieceNickName, appData.pieceNickNames);
5698 else SetCharTable(pieceNickName, "............");
5701 switch (gameInfo.variant) {
5702 case VariantFischeRandom:
5703 shuffleOpenings = TRUE;
5706 case VariantShatranj:
5707 pieces = ShatranjArray;
5708 nrCastlingRights = 0;
5709 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5712 pieces = makrukArray;
5713 nrCastlingRights = 0;
5714 startedFromSetupPosition = TRUE;
5715 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5717 case VariantTwoKings:
5718 pieces = twoKingsArray;
5720 case VariantCapaRandom:
5721 shuffleOpenings = TRUE;
5722 case VariantCapablanca:
5723 pieces = CapablancaArray;
5724 gameInfo.boardWidth = 10;
5725 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5728 pieces = GothicArray;
5729 gameInfo.boardWidth = 10;
5730 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5733 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5734 gameInfo.holdingsSize = 7;
5737 pieces = JanusArray;
5738 gameInfo.boardWidth = 10;
5739 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5740 nrCastlingRights = 6;
5741 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5742 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5743 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5744 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5745 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5746 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5749 pieces = FalconArray;
5750 gameInfo.boardWidth = 10;
5751 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5753 case VariantXiangqi:
5754 pieces = XiangqiArray;
5755 gameInfo.boardWidth = 9;
5756 gameInfo.boardHeight = 10;
5757 nrCastlingRights = 0;
5758 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5761 pieces = ShogiArray;
5762 gameInfo.boardWidth = 9;
5763 gameInfo.boardHeight = 9;
5764 gameInfo.holdingsSize = 7;
5765 nrCastlingRights = 0;
5766 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5768 case VariantCourier:
5769 pieces = CourierArray;
5770 gameInfo.boardWidth = 12;
5771 nrCastlingRights = 0;
5772 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5774 case VariantKnightmate:
5775 pieces = KnightmateArray;
5776 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5778 case VariantSpartan:
5779 pieces = SpartanArray;
5780 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5783 pieces = fairyArray;
5784 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5787 pieces = GreatArray;
5788 gameInfo.boardWidth = 10;
5789 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5790 gameInfo.holdingsSize = 8;
5794 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5795 gameInfo.holdingsSize = 8;
5796 startedFromSetupPosition = TRUE;
5798 case VariantCrazyhouse:
5799 case VariantBughouse:
5801 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5802 gameInfo.holdingsSize = 5;
5804 case VariantWildCastle:
5806 /* !!?shuffle with kings guaranteed to be on d or e file */
5807 shuffleOpenings = 1;
5809 case VariantNoCastle:
5811 nrCastlingRights = 0;
5812 /* !!?unconstrained back-rank shuffle */
5813 shuffleOpenings = 1;
5818 if(appData.NrFiles >= 0) {
5819 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5820 gameInfo.boardWidth = appData.NrFiles;
5822 if(appData.NrRanks >= 0) {
5823 gameInfo.boardHeight = appData.NrRanks;
5825 if(appData.holdingsSize >= 0) {
5826 i = appData.holdingsSize;
5827 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5828 gameInfo.holdingsSize = i;
5830 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5831 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5832 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5834 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5835 if(pawnRow < 1) pawnRow = 1;
5836 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5838 /* User pieceToChar list overrules defaults */
5839 if(appData.pieceToCharTable != NULL)
5840 SetCharTable(pieceToChar, appData.pieceToCharTable);
5842 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5844 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5845 s = (ChessSquare) 0; /* account holding counts in guard band */
5846 for( i=0; i<BOARD_HEIGHT; i++ )
5847 initialPosition[i][j] = s;
5849 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5850 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5851 initialPosition[pawnRow][j] = WhitePawn;
5852 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5853 if(gameInfo.variant == VariantXiangqi) {
5855 initialPosition[pawnRow][j] =
5856 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5857 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5858 initialPosition[2][j] = WhiteCannon;
5859 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5863 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5865 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5868 initialPosition[1][j] = WhiteBishop;
5869 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5871 initialPosition[1][j] = WhiteRook;
5872 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5875 if( nrCastlingRights == -1) {
5876 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5877 /* This sets default castling rights from none to normal corners */
5878 /* Variants with other castling rights must set them themselves above */
5879 nrCastlingRights = 6;
5881 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5884 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5889 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5890 if(gameInfo.variant == VariantGreat) { // promotion commoners
5891 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5892 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5893 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5894 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5896 if( gameInfo.variant == VariantSChess ) {
5897 initialPosition[1][0] = BlackMarshall;
5898 initialPosition[2][0] = BlackAngel;
5899 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5900 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5901 initialPosition[1][1] = initialPosition[2][1] =
5902 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5904 if (appData.debugMode) {
5905 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5907 if(shuffleOpenings) {
5908 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5909 startedFromSetupPosition = TRUE;
5911 if(startedFromPositionFile) {
5912 /* [HGM] loadPos: use PositionFile for every new game */
5913 CopyBoard(initialPosition, filePosition);
5914 for(i=0; i<nrCastlingRights; i++)
5915 initialRights[i] = filePosition[CASTLING][i];
5916 startedFromSetupPosition = TRUE;
5919 CopyBoard(boards[0], initialPosition);
5921 if(oldx != gameInfo.boardWidth ||
5922 oldy != gameInfo.boardHeight ||
5923 oldv != gameInfo.variant ||
5924 oldh != gameInfo.holdingsWidth
5926 InitDrawingSizes(-2 ,0);
5928 oldv = gameInfo.variant;
5930 DrawPosition(TRUE, boards[currentMove]);
5934 SendBoard(cps, moveNum)
5935 ChessProgramState *cps;
5938 char message[MSG_SIZ];
5940 if (cps->useSetboard) {
5941 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5942 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5943 SendToProgram(message, cps);
5949 /* Kludge to set black to move, avoiding the troublesome and now
5950 * deprecated "black" command.
5952 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5953 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5955 SendToProgram("edit\n", cps);
5956 SendToProgram("#\n", cps);
5957 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5958 bp = &boards[moveNum][i][BOARD_LEFT];
5959 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5960 if ((int) *bp < (int) BlackPawn) {
5961 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5963 if(message[0] == '+' || message[0] == '~') {
5964 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5965 PieceToChar((ChessSquare)(DEMOTED *bp)),
5968 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5969 message[1] = BOARD_RGHT - 1 - j + '1';
5970 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5972 SendToProgram(message, cps);
5977 SendToProgram("c\n", cps);
5978 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5979 bp = &boards[moveNum][i][BOARD_LEFT];
5980 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5981 if (((int) *bp != (int) EmptySquare)
5982 && ((int) *bp >= (int) BlackPawn)) {
5983 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5985 if(message[0] == '+' || message[0] == '~') {
5986 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5987 PieceToChar((ChessSquare)(DEMOTED *bp)),
5990 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5991 message[1] = BOARD_RGHT - 1 - j + '1';
5992 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5994 SendToProgram(message, cps);
5999 SendToProgram(".\n", cps);
6001 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6005 DefaultPromoChoice(int white)
6008 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6009 result = WhiteFerz; // no choice
6010 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6011 result= WhiteKing; // in Suicide Q is the last thing we want
6012 else if(gameInfo.variant == VariantSpartan)
6013 result = white ? WhiteQueen : WhiteAngel;
6014 else result = WhiteQueen;
6015 if(!white) result = WHITE_TO_BLACK result;
6019 static int autoQueen; // [HGM] oneclick
6022 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6024 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6025 /* [HGM] add Shogi promotions */
6026 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6031 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6032 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6034 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6035 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6038 piece = boards[currentMove][fromY][fromX];
6039 if(gameInfo.variant == VariantShogi) {
6040 promotionZoneSize = BOARD_HEIGHT/3;
6041 highestPromotingPiece = (int)WhiteFerz;
6042 } else if(gameInfo.variant == VariantMakruk) {
6043 promotionZoneSize = 3;
6046 // Treat Lance as Pawn when it is not representing Amazon
6047 if(gameInfo.variant != VariantSuper) {
6048 if(piece == WhiteLance) piece = WhitePawn; else
6049 if(piece == BlackLance) piece = BlackPawn;
6052 // next weed out all moves that do not touch the promotion zone at all
6053 if((int)piece >= BlackPawn) {
6054 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6056 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6058 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6059 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6062 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6064 // weed out mandatory Shogi promotions
6065 if(gameInfo.variant == VariantShogi) {
6066 if(piece >= BlackPawn) {
6067 if(toY == 0 && piece == BlackPawn ||
6068 toY == 0 && piece == BlackQueen ||
6069 toY <= 1 && piece == BlackKnight) {
6074 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6075 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6076 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6083 // weed out obviously illegal Pawn moves
6084 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6085 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6086 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6087 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6088 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6089 // note we are not allowed to test for valid (non-)capture, due to premove
6092 // we either have a choice what to promote to, or (in Shogi) whether to promote
6093 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6094 *promoChoice = PieceToChar(BlackFerz); // no choice
6097 // no sense asking what we must promote to if it is going to explode...
6098 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6099 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6102 // give caller the default choice even if we will not make it
6103 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6104 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6105 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6106 && gameInfo.variant != VariantShogi
6107 && gameInfo.variant != VariantSuper) return FALSE;
6108 if(autoQueen) return FALSE; // predetermined
6110 // suppress promotion popup on illegal moves that are not premoves
6111 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6112 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6113 if(appData.testLegality && !premove) {
6114 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6115 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6116 if(moveType != WhitePromotion && moveType != BlackPromotion)
6124 InPalace(row, column)
6126 { /* [HGM] for Xiangqi */
6127 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6128 column < (BOARD_WIDTH + 4)/2 &&
6129 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6134 PieceForSquare (x, y)
6138 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6141 return boards[currentMove][y][x];
6145 OKToStartUserMove(x, y)
6148 ChessSquare from_piece;
6151 if (matchMode) return FALSE;
6152 if (gameMode == EditPosition) return TRUE;
6154 if (x >= 0 && y >= 0)
6155 from_piece = boards[currentMove][y][x];
6157 from_piece = EmptySquare;
6159 if (from_piece == EmptySquare) return FALSE;
6161 white_piece = (int)from_piece >= (int)WhitePawn &&
6162 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6165 case PlayFromGameFile:
6167 case TwoMachinesPlay:
6175 case MachinePlaysWhite:
6176 case IcsPlayingBlack:
6177 if (appData.zippyPlay) return FALSE;
6179 DisplayMoveError(_("You are playing Black"));
6184 case MachinePlaysBlack:
6185 case IcsPlayingWhite:
6186 if (appData.zippyPlay) return FALSE;
6188 DisplayMoveError(_("You are playing White"));
6194 if (!white_piece && WhiteOnMove(currentMove)) {
6195 DisplayMoveError(_("It is White's turn"));
6198 if (white_piece && !WhiteOnMove(currentMove)) {
6199 DisplayMoveError(_("It is Black's turn"));
6202 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6203 /* Editing correspondence game history */
6204 /* Could disallow this or prompt for confirmation */
6209 case BeginningOfGame:
6210 if (appData.icsActive) return FALSE;
6211 if (!appData.noChessProgram) {
6213 DisplayMoveError(_("You are playing White"));
6220 if (!white_piece && WhiteOnMove(currentMove)) {
6221 DisplayMoveError(_("It is White's turn"));
6224 if (white_piece && !WhiteOnMove(currentMove)) {
6225 DisplayMoveError(_("It is Black's turn"));
6234 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6235 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6236 && gameMode != AnalyzeFile && gameMode != Training) {
6237 DisplayMoveError(_("Displayed position is not current"));
6244 OnlyMove(int *x, int *y, Boolean captures) {
6245 DisambiguateClosure cl;
6246 if (appData.zippyPlay) return FALSE;
6248 case MachinePlaysBlack:
6249 case IcsPlayingWhite:
6250 case BeginningOfGame:
6251 if(!WhiteOnMove(currentMove)) return FALSE;
6253 case MachinePlaysWhite:
6254 case IcsPlayingBlack:
6255 if(WhiteOnMove(currentMove)) return FALSE;
6262 cl.pieceIn = EmptySquare;
6267 cl.promoCharIn = NULLCHAR;
6268 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6269 if( cl.kind == NormalMove ||
6270 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6271 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6272 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6279 if(cl.kind != ImpossibleMove) return FALSE;
6280 cl.pieceIn = EmptySquare;
6285 cl.promoCharIn = NULLCHAR;
6286 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6287 if( cl.kind == NormalMove ||
6288 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6289 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6290 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6295 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6303 int lastLoadGameUseList = FALSE;
6304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6305 ChessMove lastLoadGameStart = EndOfFile;
6308 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6309 int fromX, fromY, toX, toY;
6313 ChessSquare pdown, pup;
6315 /* Check if the user is playing in turn. This is complicated because we
6316 let the user "pick up" a piece before it is his turn. So the piece he
6317 tried to pick up may have been captured by the time he puts it down!
6318 Therefore we use the color the user is supposed to be playing in this
6319 test, not the color of the piece that is currently on the starting
6320 square---except in EditGame mode, where the user is playing both
6321 sides; fortunately there the capture race can't happen. (It can
6322 now happen in IcsExamining mode, but that's just too bad. The user
6323 will get a somewhat confusing message in that case.)
6327 case PlayFromGameFile:
6329 case TwoMachinesPlay:
6333 /* We switched into a game mode where moves are not accepted,
6334 perhaps while the mouse button was down. */
6337 case MachinePlaysWhite:
6338 /* User is moving for Black */
6339 if (WhiteOnMove(currentMove)) {
6340 DisplayMoveError(_("It is White's turn"));
6345 case MachinePlaysBlack:
6346 /* User is moving for White */
6347 if (!WhiteOnMove(currentMove)) {
6348 DisplayMoveError(_("It is Black's turn"));
6355 case BeginningOfGame:
6358 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6359 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6360 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6361 /* User is moving for Black */
6362 if (WhiteOnMove(currentMove)) {
6363 DisplayMoveError(_("It is White's turn"));
6367 /* User is moving for White */
6368 if (!WhiteOnMove(currentMove)) {
6369 DisplayMoveError(_("It is Black's turn"));
6375 case IcsPlayingBlack:
6376 /* User is moving for Black */
6377 if (WhiteOnMove(currentMove)) {
6378 if (!appData.premove) {
6379 DisplayMoveError(_("It is White's turn"));
6380 } else if (toX >= 0 && toY >= 0) {
6383 premoveFromX = fromX;
6384 premoveFromY = fromY;
6385 premovePromoChar = promoChar;
6387 if (appData.debugMode)
6388 fprintf(debugFP, "Got premove: fromX %d,"
6389 "fromY %d, toX %d, toY %d\n",
6390 fromX, fromY, toX, toY);
6396 case IcsPlayingWhite:
6397 /* User is moving for White */
6398 if (!WhiteOnMove(currentMove)) {
6399 if (!appData.premove) {
6400 DisplayMoveError(_("It is Black's turn"));
6401 } else if (toX >= 0 && toY >= 0) {
6404 premoveFromX = fromX;
6405 premoveFromY = fromY;
6406 premovePromoChar = promoChar;
6408 if (appData.debugMode)
6409 fprintf(debugFP, "Got premove: fromX %d,"
6410 "fromY %d, toX %d, toY %d\n",
6411 fromX, fromY, toX, toY);
6421 /* EditPosition, empty square, or different color piece;
6422 click-click move is possible */
6423 if (toX == -2 || toY == -2) {
6424 boards[0][fromY][fromX] = EmptySquare;
6425 DrawPosition(FALSE, boards[currentMove]);
6427 } else if (toX >= 0 && toY >= 0) {
6428 boards[0][toY][toX] = boards[0][fromY][fromX];
6429 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6430 if(boards[0][fromY][0] != EmptySquare) {
6431 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6432 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6435 if(fromX == BOARD_RGHT+1) {
6436 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6437 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6438 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6441 boards[0][fromY][fromX] = EmptySquare;
6442 DrawPosition(FALSE, boards[currentMove]);
6448 if(toX < 0 || toY < 0) return;
6449 pdown = boards[currentMove][fromY][fromX];
6450 pup = boards[currentMove][toY][toX];
6452 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6453 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6454 if( pup != EmptySquare ) return;
6455 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6456 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6457 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6458 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6459 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6460 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6461 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6465 /* [HGM] always test for legality, to get promotion info */
6466 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6467 fromY, fromX, toY, toX, promoChar);
6468 /* [HGM] but possibly ignore an IllegalMove result */
6469 if (appData.testLegality) {
6470 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6471 DisplayMoveError(_("Illegal move"));
6476 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6479 /* Common tail of UserMoveEvent and DropMenuEvent */
6481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6483 int fromX, fromY, toX, toY;
6484 /*char*/int promoChar;
6488 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6489 // [HGM] superchess: suppress promotions to non-available piece
6490 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6491 if(WhiteOnMove(currentMove)) {
6492 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6494 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6498 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6499 move type in caller when we know the move is a legal promotion */
6500 if(moveType == NormalMove && promoChar)
6501 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6503 /* [HGM] <popupFix> The following if has been moved here from
6504 UserMoveEvent(). Because it seemed to belong here (why not allow
6505 piece drops in training games?), and because it can only be
6506 performed after it is known to what we promote. */
6507 if (gameMode == Training) {
6508 /* compare the move played on the board to the next move in the
6509 * game. If they match, display the move and the opponent's response.
6510 * If they don't match, display an error message.
6514 CopyBoard(testBoard, boards[currentMove]);
6515 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6517 if (CompareBoards(testBoard, boards[currentMove+1])) {
6518 ForwardInner(currentMove+1);
6520 /* Autoplay the opponent's response.
6521 * if appData.animate was TRUE when Training mode was entered,
6522 * the response will be animated.
6524 saveAnimate = appData.animate;
6525 appData.animate = animateTraining;
6526 ForwardInner(currentMove+1);
6527 appData.animate = saveAnimate;
6529 /* check for the end of the game */
6530 if (currentMove >= forwardMostMove) {
6531 gameMode = PlayFromGameFile;
6533 SetTrainingModeOff();
6534 DisplayInformation(_("End of game"));
6537 DisplayError(_("Incorrect move"), 0);
6542 /* Ok, now we know that the move is good, so we can kill
6543 the previous line in Analysis Mode */
6544 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6545 && currentMove < forwardMostMove) {
6546 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6547 else forwardMostMove = currentMove;
6550 /* If we need the chess program but it's dead, restart it */
6551 ResurrectChessProgram();
6553 /* A user move restarts a paused game*/
6557 thinkOutput[0] = NULLCHAR;
6559 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6561 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6562 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6566 if (gameMode == BeginningOfGame) {
6567 if (appData.noChessProgram) {
6568 gameMode = EditGame;
6572 gameMode = MachinePlaysBlack;
6575 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6577 if (first.sendName) {
6578 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6579 SendToProgram(buf, &first);
6586 /* Relay move to ICS or chess engine */
6587 if (appData.icsActive) {
6588 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6589 gameMode == IcsExamining) {
6590 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6591 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6593 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6595 // also send plain move, in case ICS does not understand atomic claims
6596 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6600 if (first.sendTime && (gameMode == BeginningOfGame ||
6601 gameMode == MachinePlaysWhite ||
6602 gameMode == MachinePlaysBlack)) {
6603 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6605 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6606 // [HGM] book: if program might be playing, let it use book
6607 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6608 first.maybeThinking = TRUE;
6609 } else SendMoveToProgram(forwardMostMove-1, &first);
6610 if (currentMove == cmailOldMove + 1) {
6611 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6615 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6619 if(appData.testLegality)
6620 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6626 if (WhiteOnMove(currentMove)) {
6627 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6629 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6633 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6638 case MachinePlaysBlack:
6639 case MachinePlaysWhite:
6640 /* disable certain menu options while machine is thinking */
6641 SetMachineThinkingEnables();
6648 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6649 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6651 if(bookHit) { // [HGM] book: simulate book reply
6652 static char bookMove[MSG_SIZ]; // a bit generous?
6654 programStats.nodes = programStats.depth = programStats.time =
6655 programStats.score = programStats.got_only_move = 0;
6656 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6658 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6659 strcat(bookMove, bookHit);
6660 HandleMachineMove(bookMove, &first);
6666 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6673 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6674 Markers *m = (Markers *) closure;
6675 if(rf == fromY && ff == fromX)
6676 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6677 || kind == WhiteCapturesEnPassant
6678 || kind == BlackCapturesEnPassant);
6679 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6683 MarkTargetSquares(int clear)
6686 if(!appData.markers || !appData.highlightDragging ||
6687 !appData.testLegality || gameMode == EditPosition) return;
6689 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6692 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6693 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6694 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6696 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6699 DrawPosition(TRUE, NULL);
6703 Explode(Board board, int fromX, int fromY, int toX, int toY)
6705 if(gameInfo.variant == VariantAtomic &&
6706 (board[toY][toX] != EmptySquare || // capture?
6707 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6708 board[fromY][fromX] == BlackPawn )
6710 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6716 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6718 int CanPromote(ChessSquare piece, int y)
6720 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6721 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6722 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6723 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6724 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6725 gameInfo.variant == VariantMakruk) return FALSE;
6726 return (piece == BlackPawn && y == 1 ||
6727 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6728 piece == BlackLance && y == 1 ||
6729 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6732 void LeftClick(ClickType clickType, int xPix, int yPix)
6735 Boolean saveAnimate;
6736 static int second = 0, promotionChoice = 0, clearFlag = 0;
6737 char promoChoice = NULLCHAR;
6740 if(appData.seekGraph && appData.icsActive && loggedOn &&
6741 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6742 SeekGraphClick(clickType, xPix, yPix, 0);
6746 if (clickType == Press) ErrorPopDown();
6747 MarkTargetSquares(1);
6749 x = EventToSquare(xPix, BOARD_WIDTH);
6750 y = EventToSquare(yPix, BOARD_HEIGHT);
6751 if (!flipView && y >= 0) {
6752 y = BOARD_HEIGHT - 1 - y;
6754 if (flipView && x >= 0) {
6755 x = BOARD_WIDTH - 1 - x;
6758 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6759 defaultPromoChoice = promoSweep;
6760 promoSweep = EmptySquare; // terminate sweep
6761 promoDefaultAltered = TRUE;
6762 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6765 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6766 if(clickType == Release) return; // ignore upclick of click-click destination
6767 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6768 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6769 if(gameInfo.holdingsWidth &&
6770 (WhiteOnMove(currentMove)
6771 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6772 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6773 // click in right holdings, for determining promotion piece
6774 ChessSquare p = boards[currentMove][y][x];
6775 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6776 if(p != EmptySquare) {
6777 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6782 DrawPosition(FALSE, boards[currentMove]);
6786 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6787 if(clickType == Press
6788 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6789 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6790 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6793 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6794 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6796 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6797 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6798 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6799 defaultPromoChoice = DefaultPromoChoice(side);
6802 autoQueen = appData.alwaysPromoteToQueen;
6806 gatingPiece = EmptySquare;
6807 if (clickType != Press) {
6808 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6809 DragPieceEnd(xPix, yPix); dragging = 0;
6810 DrawPosition(FALSE, NULL);
6814 fromX = x; fromY = y;
6815 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6816 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6817 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6819 if (OKToStartUserMove(fromX, fromY)) {
6821 MarkTargetSquares(0);
6822 DragPieceBegin(xPix, yPix); dragging = 1;
6823 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6824 promoSweep = defaultPromoChoice;
6825 selectFlag = 0; lastX = xPix; lastY = yPix;
6826 Sweep(0); // Pawn that is going to promote: preview promotion piece
6827 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6829 if (appData.highlightDragging) {
6830 SetHighlights(fromX, fromY, -1, -1);
6832 } else fromX = fromY = -1;
6838 if (clickType == Press && gameMode != EditPosition) {
6843 // ignore off-board to clicks
6844 if(y < 0 || x < 0) return;
6846 /* Check if clicking again on the same color piece */
6847 fromP = boards[currentMove][fromY][fromX];
6848 toP = boards[currentMove][y][x];
6849 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6850 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6851 WhitePawn <= toP && toP <= WhiteKing &&
6852 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6853 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6854 (BlackPawn <= fromP && fromP <= BlackKing &&
6855 BlackPawn <= toP && toP <= BlackKing &&
6856 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6857 !(fromP == BlackKing && toP == BlackRook && frc))) {
6858 /* Clicked again on same color piece -- changed his mind */
6859 second = (x == fromX && y == fromY);
6860 promoDefaultAltered = FALSE;
6861 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6862 if (appData.highlightDragging) {
6863 SetHighlights(x, y, -1, -1);
6867 if (OKToStartUserMove(x, y)) {
6868 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6869 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6870 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6871 gatingPiece = boards[currentMove][fromY][fromX];
6872 else gatingPiece = EmptySquare;
6874 fromY = y; dragging = 1;
6875 MarkTargetSquares(0);
6876 DragPieceBegin(xPix, yPix);
6877 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6878 promoSweep = defaultPromoChoice;
6879 selectFlag = 0; lastX = xPix; lastY = yPix;
6880 Sweep(0); // Pawn that is going to promote: preview promotion piece
6884 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6887 // ignore clicks on holdings
6888 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6891 if (clickType == Release && x == fromX && y == fromY) {
6892 DragPieceEnd(xPix, yPix); dragging = 0;
6894 // a deferred attempt to click-click move an empty square on top of a piece
6895 boards[currentMove][y][x] = EmptySquare;
6897 DrawPosition(FALSE, boards[currentMove]);
6898 fromX = fromY = -1; clearFlag = 0;
6901 if (appData.animateDragging) {
6902 /* Undo animation damage if any */
6903 DrawPosition(FALSE, NULL);
6906 /* Second up/down in same square; just abort move */
6909 gatingPiece = EmptySquare;
6912 ClearPremoveHighlights();
6914 /* First upclick in same square; start click-click mode */
6915 SetHighlights(x, y, -1, -1);
6922 /* we now have a different from- and (possibly off-board) to-square */
6923 /* Completed move */
6926 saveAnimate = appData.animate;
6927 if (clickType == Press) {
6928 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6929 // must be Edit Position mode with empty-square selected
6930 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6931 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6934 /* Finish clickclick move */
6935 if (appData.animate || appData.highlightLastMove) {
6936 SetHighlights(fromX, fromY, toX, toY);
6941 /* Finish drag move */
6942 if (appData.highlightLastMove) {
6943 SetHighlights(fromX, fromY, toX, toY);
6947 DragPieceEnd(xPix, yPix); dragging = 0;
6948 /* Don't animate move and drag both */
6949 appData.animate = FALSE;
6952 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6953 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6954 ChessSquare piece = boards[currentMove][fromY][fromX];
6955 if(gameMode == EditPosition && piece != EmptySquare &&
6956 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6959 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6960 n = PieceToNumber(piece - (int)BlackPawn);
6961 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6962 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6963 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6965 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6966 n = PieceToNumber(piece);
6967 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6968 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6969 boards[currentMove][n][BOARD_WIDTH-2]++;
6971 boards[currentMove][fromY][fromX] = EmptySquare;
6975 DrawPosition(TRUE, boards[currentMove]);
6979 // off-board moves should not be highlighted
6980 if(x < 0 || y < 0) ClearHighlights();
6982 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6984 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6985 SetHighlights(fromX, fromY, toX, toY);
6986 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6987 // [HGM] super: promotion to captured piece selected from holdings
6988 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6989 promotionChoice = TRUE;
6990 // kludge follows to temporarily execute move on display, without promoting yet
6991 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6992 boards[currentMove][toY][toX] = p;
6993 DrawPosition(FALSE, boards[currentMove]);
6994 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6995 boards[currentMove][toY][toX] = q;
6996 DisplayMessage("Click in holdings to choose piece", "");
7001 int oldMove = currentMove;
7002 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7003 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7004 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7005 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7006 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7007 DrawPosition(TRUE, boards[currentMove]);
7010 appData.animate = saveAnimate;
7011 if (appData.animate || appData.animateDragging) {
7012 /* Undo animation damage if needed */
7013 DrawPosition(FALSE, NULL);
7017 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7018 { // front-end-free part taken out of PieceMenuPopup
7019 int whichMenu; int xSqr, ySqr;
7021 if(seekGraphUp) { // [HGM] seekgraph
7022 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7023 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7027 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7028 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7029 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7030 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7031 if(action == Press) {
7032 originalFlip = flipView;
7033 flipView = !flipView; // temporarily flip board to see game from partners perspective
7034 DrawPosition(TRUE, partnerBoard);
7035 DisplayMessage(partnerStatus, "");
7037 } else if(action == Release) {
7038 flipView = originalFlip;
7039 DrawPosition(TRUE, boards[currentMove]);
7045 xSqr = EventToSquare(x, BOARD_WIDTH);
7046 ySqr = EventToSquare(y, BOARD_HEIGHT);
7047 if (action == Release) {
7048 if(pieceSweep != EmptySquare) {
7049 EditPositionMenuEvent(pieceSweep, toX, toY);
7050 pieceSweep = EmptySquare;
7051 } else UnLoadPV(); // [HGM] pv
7053 if (action != Press) return -2; // return code to be ignored
7056 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7058 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7059 if (xSqr < 0 || ySqr < 0) return -1;
7060 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7061 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7062 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7063 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7067 if(!appData.icsEngineAnalyze) return -1;
7068 case IcsPlayingWhite:
7069 case IcsPlayingBlack:
7070 if(!appData.zippyPlay) goto noZip;
7073 case MachinePlaysWhite:
7074 case MachinePlaysBlack:
7075 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7076 if (!appData.dropMenu) {
7078 return 2; // flag front-end to grab mouse events
7080 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7081 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7084 if (xSqr < 0 || ySqr < 0) return -1;
7085 if (!appData.dropMenu || appData.testLegality &&
7086 gameInfo.variant != VariantBughouse &&
7087 gameInfo.variant != VariantCrazyhouse) return -1;
7088 whichMenu = 1; // drop menu
7094 if (((*fromX = xSqr) < 0) ||
7095 ((*fromY = ySqr) < 0)) {
7096 *fromX = *fromY = -1;
7100 *fromX = BOARD_WIDTH - 1 - *fromX;
7102 *fromY = BOARD_HEIGHT - 1 - *fromY;
7107 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7109 // char * hint = lastHint;
7110 FrontEndProgramStats stats;
7112 stats.which = cps == &first ? 0 : 1;
7113 stats.depth = cpstats->depth;
7114 stats.nodes = cpstats->nodes;
7115 stats.score = cpstats->score;
7116 stats.time = cpstats->time;
7117 stats.pv = cpstats->movelist;
7118 stats.hint = lastHint;
7119 stats.an_move_index = 0;
7120 stats.an_move_count = 0;
7122 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7123 stats.hint = cpstats->move_name;
7124 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7125 stats.an_move_count = cpstats->nr_moves;
7128 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
7130 SetProgramStats( &stats );
7133 #define MAXPLAYERS 500
7136 TourneyStandings(int display)
7138 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7139 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7140 char result, *p, *names[MAXPLAYERS];
7142 if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7144 names[0] = p = strdup(appData.participants);
7145 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7147 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7149 while(result = appData.results[nr]) {
7150 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7151 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7152 wScore = bScore = 0;
7154 case '+': wScore = 2; break;
7155 case '-': bScore = 2; break;
7156 case '=': wScore = bScore = 1; break;
7158 case '*': return strdup("busy"); // tourney not finished
7166 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7167 for(w=0; w<nPlayers; w++) {
7169 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7170 ranking[w] = b; points[w] = bScore; score[b] = -2;
7172 p = malloc(nPlayers*34+1);
7173 for(w=0; w<nPlayers && w<display; w++)
7174 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7180 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7181 { // count all piece types
7183 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7184 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7185 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7188 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7189 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7190 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7191 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7192 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7193 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7198 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7200 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7201 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7203 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7204 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7205 if(myPawns == 2 && nMine == 3) // KPP
7206 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7207 if(myPawns == 1 && nMine == 2) // KP
7208 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7209 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7210 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7211 if(myPawns) return FALSE;
7212 if(pCnt[WhiteRook+side])
7213 return pCnt[BlackRook-side] ||
7214 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7215 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7216 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7217 if(pCnt[WhiteCannon+side]) {
7218 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7219 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7221 if(pCnt[WhiteKnight+side])
7222 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7227 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7229 VariantClass v = gameInfo.variant;
7231 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7232 if(v == VariantShatranj) return TRUE; // always winnable through baring
7233 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7234 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7236 if(v == VariantXiangqi) {
7237 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7239 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7240 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7241 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7242 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7243 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7244 if(stale) // we have at least one last-rank P plus perhaps C
7245 return majors // KPKX
7246 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7248 return pCnt[WhiteFerz+side] // KCAK
7249 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7250 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7251 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7253 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7254 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7256 if(nMine == 1) return FALSE; // bare King
7257 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
7258 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7259 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7260 // by now we have King + 1 piece (or multiple Bishops on the same color)
7261 if(pCnt[WhiteKnight+side])
7262 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7263 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7264 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7266 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7267 if(pCnt[WhiteAlfil+side])
7268 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7269 if(pCnt[WhiteWazir+side])
7270 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7277 Adjudicate(ChessProgramState *cps)
7278 { // [HGM] some adjudications useful with buggy engines
7279 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7280 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7281 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7282 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7283 int k, count = 0; static int bare = 1;
7284 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7285 Boolean canAdjudicate = !appData.icsActive;
7287 // most tests only when we understand the game, i.e. legality-checking on
7288 if( appData.testLegality )
7289 { /* [HGM] Some more adjudications for obstinate engines */
7290 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7291 static int moveCount = 6;
7293 char *reason = NULL;
7295 /* Count what is on board. */
7296 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7298 /* Some material-based adjudications that have to be made before stalemate test */
7299 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7300 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7301 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7302 if(canAdjudicate && appData.checkMates) {
7304 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7305 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7306 "Xboard adjudication: King destroyed", GE_XBOARD );
7311 /* Bare King in Shatranj (loses) or Losers (wins) */
7312 if( nrW == 1 || nrB == 1) {
7313 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7314 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7315 if(canAdjudicate && appData.checkMates) {
7317 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7318 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7319 "Xboard adjudication: Bare king", GE_XBOARD );
7323 if( gameInfo.variant == VariantShatranj && --bare < 0)
7325 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7326 if(canAdjudicate && appData.checkMates) {
7327 /* but only adjudicate if adjudication enabled */
7329 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7330 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7331 "Xboard adjudication: Bare king", GE_XBOARD );
7338 // don't wait for engine to announce game end if we can judge ourselves
7339 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7341 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7342 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7343 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7344 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7347 reason = "Xboard adjudication: 3rd check";
7348 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7358 reason = "Xboard adjudication: Stalemate";
7359 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7360 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7361 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7362 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7363 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7364 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7365 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7366 EP_CHECKMATE : EP_WINS);
7367 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7368 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7372 reason = "Xboard adjudication: Checkmate";
7373 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7377 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7379 result = GameIsDrawn; break;
7381 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7383 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7387 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7389 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7390 GameEnds( result, reason, GE_XBOARD );
7394 /* Next absolutely insufficient mating material. */
7395 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7396 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7397 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7399 /* always flag draws, for judging claims */
7400 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7402 if(canAdjudicate && appData.materialDraws) {
7403 /* but only adjudicate them if adjudication enabled */
7404 if(engineOpponent) {
7405 SendToProgram("force\n", engineOpponent); // suppress reply
7406 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7408 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7413 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7414 if(gameInfo.variant == VariantXiangqi ?
7415 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7417 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7418 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7419 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7420 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7422 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7423 { /* if the first 3 moves do not show a tactical win, declare draw */
7424 if(engineOpponent) {
7425 SendToProgram("force\n", engineOpponent); // suppress reply
7426 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7428 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7431 } else moveCount = 6;
7433 if (appData.debugMode) { int i;
7434 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7435 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7436 appData.drawRepeats);
7437 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7438 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7442 // Repetition draws and 50-move rule can be applied independently of legality testing
7444 /* Check for rep-draws */
7446 for(k = forwardMostMove-2;
7447 k>=backwardMostMove && k>=forwardMostMove-100 &&
7448 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7449 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7452 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7453 /* compare castling rights */
7454 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7455 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7456 rights++; /* King lost rights, while rook still had them */
7457 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7458 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7459 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7460 rights++; /* but at least one rook lost them */
7462 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7463 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7465 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7466 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7467 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7470 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7471 && appData.drawRepeats > 1) {
7472 /* adjudicate after user-specified nr of repeats */
7473 int result = GameIsDrawn;
7474 char *details = "XBoard adjudication: repetition draw";
7475 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7476 // [HGM] xiangqi: check for forbidden perpetuals
7477 int m, ourPerpetual = 1, hisPerpetual = 1;
7478 for(m=forwardMostMove; m>k; m-=2) {
7479 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7480 ourPerpetual = 0; // the current mover did not always check
7481 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7482 hisPerpetual = 0; // the opponent did not always check
7484 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7485 ourPerpetual, hisPerpetual);
7486 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7487 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7488 details = "Xboard adjudication: perpetual checking";
7490 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7491 break; // (or we would have caught him before). Abort repetition-checking loop.
7493 // Now check for perpetual chases
7494 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7495 hisPerpetual = PerpetualChase(k, forwardMostMove);
7496 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7497 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7498 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7499 details = "Xboard adjudication: perpetual chasing";
7501 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7502 break; // Abort repetition-checking loop.
7504 // if neither of us is checking or chasing all the time, or both are, it is draw
7506 if(engineOpponent) {
7507 SendToProgram("force\n", engineOpponent); // suppress reply
7508 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7510 GameEnds( result, details, GE_XBOARD );
7513 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7514 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7518 /* Now we test for 50-move draws. Determine ply count */
7519 count = forwardMostMove;
7520 /* look for last irreversble move */
7521 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7523 /* if we hit starting position, add initial plies */
7524 if( count == backwardMostMove )
7525 count -= initialRulePlies;
7526 count = forwardMostMove - count;
7527 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7528 // adjust reversible move counter for checks in Xiangqi
7529 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7530 if(i < backwardMostMove) i = backwardMostMove;
7531 while(i <= forwardMostMove) {
7532 lastCheck = inCheck; // check evasion does not count
7533 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7534 if(inCheck || lastCheck) count--; // check does not count
7539 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7540 /* this is used to judge if draw claims are legal */
7541 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7542 if(engineOpponent) {
7543 SendToProgram("force\n", engineOpponent); // suppress reply
7544 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7546 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7550 /* if draw offer is pending, treat it as a draw claim
7551 * when draw condition present, to allow engines a way to
7552 * claim draws before making their move to avoid a race
7553 * condition occurring after their move
7555 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7557 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7558 p = "Draw claim: 50-move rule";
7559 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7560 p = "Draw claim: 3-fold repetition";
7561 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7562 p = "Draw claim: insufficient mating material";
7563 if( p != NULL && canAdjudicate) {
7564 if(engineOpponent) {
7565 SendToProgram("force\n", engineOpponent); // suppress reply
7566 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7568 GameEnds( GameIsDrawn, p, GE_XBOARD );
7573 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7574 if(engineOpponent) {
7575 SendToProgram("force\n", engineOpponent); // suppress reply
7576 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7578 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7584 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7585 { // [HGM] book: this routine intercepts moves to simulate book replies
7586 char *bookHit = NULL;
7588 //first determine if the incoming move brings opponent into his book
7589 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7590 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7591 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7592 if(bookHit != NULL && !cps->bookSuspend) {
7593 // make sure opponent is not going to reply after receiving move to book position
7594 SendToProgram("force\n", cps);
7595 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7597 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7598 // now arrange restart after book miss
7600 // after a book hit we never send 'go', and the code after the call to this routine
7601 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7602 char buf[MSG_SIZ], *move = bookHit;
7604 int fromX, fromY, toX, toY;
7608 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7609 &fromX, &fromY, &toX, &toY, &promoChar)) {
7610 (void) CoordsToAlgebraic(boards[forwardMostMove],
7611 PosFlags(forwardMostMove),
7612 fromY, fromX, toY, toX, promoChar, move);
7614 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7618 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7619 SendToProgram(buf, cps);
7620 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7621 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7622 SendToProgram("go\n", cps);
7623 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7624 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7625 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7626 SendToProgram("go\n", cps);
7627 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7629 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7633 ChessProgramState *savedState;
7634 void DeferredBookMove(void)
7636 if(savedState->lastPing != savedState->lastPong)
7637 ScheduleDelayedEvent(DeferredBookMove, 10);
7639 HandleMachineMove(savedMessage, savedState);
7642 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7645 HandleMachineMove(message, cps)
7647 ChessProgramState *cps;
7649 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7650 char realname[MSG_SIZ];
7651 int fromX, fromY, toX, toY;
7658 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7659 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7660 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7661 pairingReceived = 1;
7663 return; // Skim the pairing messages here.
7668 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7670 * Kludge to ignore BEL characters
7672 while (*message == '\007') message++;
7675 * [HGM] engine debug message: ignore lines starting with '#' character
7677 if(cps->debug && *message == '#') return;
7680 * Look for book output
7682 if (cps == &first && bookRequested) {
7683 if (message[0] == '\t' || message[0] == ' ') {
7684 /* Part of the book output is here; append it */
7685 strcat(bookOutput, message);
7686 strcat(bookOutput, " \n");
7688 } else if (bookOutput[0] != NULLCHAR) {
7689 /* All of book output has arrived; display it */
7690 char *p = bookOutput;
7691 while (*p != NULLCHAR) {
7692 if (*p == '\t') *p = ' ';
7695 DisplayInformation(bookOutput);
7696 bookRequested = FALSE;
7697 /* Fall through to parse the current output */
7702 * Look for machine move.
7704 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7705 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7707 /* This method is only useful on engines that support ping */
7708 if (cps->lastPing != cps->lastPong) {
7709 if (gameMode == BeginningOfGame) {
7710 /* Extra move from before last new; ignore */
7711 if (appData.debugMode) {
7712 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7715 if (appData.debugMode) {
7716 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7717 cps->which, gameMode);
7720 SendToProgram("undo\n", cps);
7726 case BeginningOfGame:
7727 /* Extra move from before last reset; ignore */
7728 if (appData.debugMode) {
7729 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7736 /* Extra move after we tried to stop. The mode test is
7737 not a reliable way of detecting this problem, but it's
7738 the best we can do on engines that don't support ping.
7740 if (appData.debugMode) {
7741 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7742 cps->which, gameMode);
7744 SendToProgram("undo\n", cps);
7747 case MachinePlaysWhite:
7748 case IcsPlayingWhite:
7749 machineWhite = TRUE;
7752 case MachinePlaysBlack:
7753 case IcsPlayingBlack:
7754 machineWhite = FALSE;
7757 case TwoMachinesPlay:
7758 machineWhite = (cps->twoMachinesColor[0] == 'w');
7761 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7762 if (appData.debugMode) {
7764 "Ignoring move out of turn by %s, gameMode %d"
7765 ", forwardMost %d\n",
7766 cps->which, gameMode, forwardMostMove);
7771 if (appData.debugMode) { int f = forwardMostMove;
7772 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7773 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7774 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7776 if(cps->alphaRank) AlphaRank(machineMove, 4);
7777 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7778 &fromX, &fromY, &toX, &toY, &promoChar)) {
7779 /* Machine move could not be parsed; ignore it. */
7780 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7781 machineMove, _(cps->which));
7782 DisplayError(buf1, 0);
7783 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7784 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7785 if (gameMode == TwoMachinesPlay) {
7786 GameEnds(machineWhite ? BlackWins : WhiteWins,
7792 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7793 /* So we have to redo legality test with true e.p. status here, */
7794 /* to make sure an illegal e.p. capture does not slip through, */
7795 /* to cause a forfeit on a justified illegal-move complaint */
7796 /* of the opponent. */
7797 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7799 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7800 fromY, fromX, toY, toX, promoChar);
7801 if (appData.debugMode) {
7803 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7804 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7805 fprintf(debugFP, "castling rights\n");
7807 if(moveType == IllegalMove) {
7808 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7809 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7810 GameEnds(machineWhite ? BlackWins : WhiteWins,
7813 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7814 /* [HGM] Kludge to handle engines that send FRC-style castling
7815 when they shouldn't (like TSCP-Gothic) */
7817 case WhiteASideCastleFR:
7818 case BlackASideCastleFR:
7820 currentMoveString[2]++;
7822 case WhiteHSideCastleFR:
7823 case BlackHSideCastleFR:
7825 currentMoveString[2]--;
7827 default: ; // nothing to do, but suppresses warning of pedantic compilers
7830 hintRequested = FALSE;
7831 lastHint[0] = NULLCHAR;
7832 bookRequested = FALSE;
7833 /* Program may be pondering now */
7834 cps->maybeThinking = TRUE;
7835 if (cps->sendTime == 2) cps->sendTime = 1;
7836 if (cps->offeredDraw) cps->offeredDraw--;
7838 /* [AS] Save move info*/
7839 pvInfoList[ forwardMostMove ].score = programStats.score;
7840 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7841 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7843 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7845 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7846 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7849 while( count < adjudicateLossPlies ) {
7850 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7853 score = -score; /* Flip score for winning side */
7856 if( score > adjudicateLossThreshold ) {
7863 if( count >= adjudicateLossPlies ) {
7864 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7866 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7867 "Xboard adjudication",
7874 if(Adjudicate(cps)) {
7875 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7876 return; // [HGM] adjudicate: for all automatic game ends
7880 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7882 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7883 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7885 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7887 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7889 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7890 char buf[3*MSG_SIZ];
7892 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7893 programStats.score / 100.,
7895 programStats.time / 100.,
7896 (unsigned int)programStats.nodes,
7897 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7898 programStats.movelist);
7900 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7905 /* [AS] Clear stats for next move */
7906 ClearProgramStats();
7907 thinkOutput[0] = NULLCHAR;
7908 hiddenThinkOutputState = 0;
7911 if (gameMode == TwoMachinesPlay) {
7912 /* [HGM] relaying draw offers moved to after reception of move */
7913 /* and interpreting offer as claim if it brings draw condition */
7914 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7915 SendToProgram("draw\n", cps->other);
7917 if (cps->other->sendTime) {
7918 SendTimeRemaining(cps->other,
7919 cps->other->twoMachinesColor[0] == 'w');
7921 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7922 if (firstMove && !bookHit) {
7924 if (cps->other->useColors) {
7925 SendToProgram(cps->other->twoMachinesColor, cps->other);
7927 SendToProgram("go\n", cps->other);
7929 cps->other->maybeThinking = TRUE;
7932 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7934 if (!pausing && appData.ringBellAfterMoves) {
7939 * Reenable menu items that were disabled while
7940 * machine was thinking
7942 if (gameMode != TwoMachinesPlay)
7943 SetUserThinkingEnables();
7945 // [HGM] book: after book hit opponent has received move and is now in force mode
7946 // force the book reply into it, and then fake that it outputted this move by jumping
7947 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7949 static char bookMove[MSG_SIZ]; // a bit generous?
7951 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7952 strcat(bookMove, bookHit);
7955 programStats.nodes = programStats.depth = programStats.time =
7956 programStats.score = programStats.got_only_move = 0;
7957 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7959 if(cps->lastPing != cps->lastPong) {
7960 savedMessage = message; // args for deferred call
7962 ScheduleDelayedEvent(DeferredBookMove, 10);
7971 /* Set special modes for chess engines. Later something general
7972 * could be added here; for now there is just one kludge feature,
7973 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7974 * when "xboard" is given as an interactive command.
7976 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7977 cps->useSigint = FALSE;
7978 cps->useSigterm = FALSE;
7980 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7981 ParseFeatures(message+8, cps);
7982 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7985 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7986 int dummy, s=6; char buf[MSG_SIZ];
7987 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7988 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7989 ParseFEN(boards[0], &dummy, message+s);
7990 DrawPosition(TRUE, boards[0]);
7991 startedFromSetupPosition = TRUE;
7994 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7995 * want this, I was asked to put it in, and obliged.
7997 if (!strncmp(message, "setboard ", 9)) {
7998 Board initial_position;
8000 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8002 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8003 DisplayError(_("Bad FEN received from engine"), 0);
8007 CopyBoard(boards[0], initial_position);
8008 initialRulePlies = FENrulePlies;
8009 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8010 else gameMode = MachinePlaysBlack;
8011 DrawPosition(FALSE, boards[currentMove]);
8017 * Look for communication commands
8019 if (!strncmp(message, "telluser ", 9)) {
8020 if(message[9] == '\\' && message[10] == '\\')
8021 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8022 DisplayNote(message + 9);
8025 if (!strncmp(message, "tellusererror ", 14)) {
8027 if(message[14] == '\\' && message[15] == '\\')
8028 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8029 DisplayError(message + 14, 0);
8032 if (!strncmp(message, "tellopponent ", 13)) {
8033 if (appData.icsActive) {
8035 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8039 DisplayNote(message + 13);
8043 if (!strncmp(message, "tellothers ", 11)) {
8044 if (appData.icsActive) {
8046 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8052 if (!strncmp(message, "tellall ", 8)) {
8053 if (appData.icsActive) {
8055 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8059 DisplayNote(message + 8);
8063 if (strncmp(message, "warning", 7) == 0) {
8064 /* Undocumented feature, use tellusererror in new code */
8065 DisplayError(message, 0);
8068 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8069 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8070 strcat(realname, " query");
8071 AskQuestion(realname, buf2, buf1, cps->pr);
8074 /* Commands from the engine directly to ICS. We don't allow these to be
8075 * sent until we are logged on. Crafty kibitzes have been known to
8076 * interfere with the login process.
8079 if (!strncmp(message, "tellics ", 8)) {
8080 SendToICS(message + 8);
8084 if (!strncmp(message, "tellicsnoalias ", 15)) {
8085 SendToICS(ics_prefix);
8086 SendToICS(message + 15);
8090 /* The following are for backward compatibility only */
8091 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8092 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8093 SendToICS(ics_prefix);
8099 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8103 * If the move is illegal, cancel it and redraw the board.
8104 * Also deal with other error cases. Matching is rather loose
8105 * here to accommodate engines written before the spec.
8107 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8108 strncmp(message, "Error", 5) == 0) {
8109 if (StrStr(message, "name") ||
8110 StrStr(message, "rating") || StrStr(message, "?") ||
8111 StrStr(message, "result") || StrStr(message, "board") ||
8112 StrStr(message, "bk") || StrStr(message, "computer") ||
8113 StrStr(message, "variant") || StrStr(message, "hint") ||
8114 StrStr(message, "random") || StrStr(message, "depth") ||
8115 StrStr(message, "accepted")) {
8118 if (StrStr(message, "protover")) {
8119 /* Program is responding to input, so it's apparently done
8120 initializing, and this error message indicates it is
8121 protocol version 1. So we don't need to wait any longer
8122 for it to initialize and send feature commands. */
8123 FeatureDone(cps, 1);
8124 cps->protocolVersion = 1;
8127 cps->maybeThinking = FALSE;
8129 if (StrStr(message, "draw")) {
8130 /* Program doesn't have "draw" command */
8131 cps->sendDrawOffers = 0;
8134 if (cps->sendTime != 1 &&
8135 (StrStr(message, "time") || StrStr(message, "otim"))) {
8136 /* Program apparently doesn't have "time" or "otim" command */
8140 if (StrStr(message, "analyze")) {
8141 cps->analysisSupport = FALSE;
8142 cps->analyzing = FALSE;
8144 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8145 DisplayError(buf2, 0);
8148 if (StrStr(message, "(no matching move)st")) {
8149 /* Special kludge for GNU Chess 4 only */
8150 cps->stKludge = TRUE;
8151 SendTimeControl(cps, movesPerSession, timeControl,
8152 timeIncrement, appData.searchDepth,
8156 if (StrStr(message, "(no matching move)sd")) {
8157 /* Special kludge for GNU Chess 4 only */
8158 cps->sdKludge = TRUE;
8159 SendTimeControl(cps, movesPerSession, timeControl,
8160 timeIncrement, appData.searchDepth,
8164 if (!StrStr(message, "llegal")) {
8167 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8168 gameMode == IcsIdle) return;
8169 if (forwardMostMove <= backwardMostMove) return;
8170 if (pausing) PauseEvent();
8171 if(appData.forceIllegal) {
8172 // [HGM] illegal: machine refused move; force position after move into it
8173 SendToProgram("force\n", cps);
8174 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8175 // we have a real problem now, as SendBoard will use the a2a3 kludge
8176 // when black is to move, while there might be nothing on a2 or black
8177 // might already have the move. So send the board as if white has the move.
8178 // But first we must change the stm of the engine, as it refused the last move
8179 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8180 if(WhiteOnMove(forwardMostMove)) {
8181 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8182 SendBoard(cps, forwardMostMove); // kludgeless board
8184 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8185 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8186 SendBoard(cps, forwardMostMove+1); // kludgeless board
8188 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8189 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8190 gameMode == TwoMachinesPlay)
8191 SendToProgram("go\n", cps);
8194 if (gameMode == PlayFromGameFile) {
8195 /* Stop reading this game file */
8196 gameMode = EditGame;
8199 /* [HGM] illegal-move claim should forfeit game when Xboard */
8200 /* only passes fully legal moves */
8201 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8202 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8203 "False illegal-move claim", GE_XBOARD );
8204 return; // do not take back move we tested as valid
8206 currentMove = forwardMostMove-1;
8207 DisplayMove(currentMove-1); /* before DisplayMoveError */
8208 SwitchClocks(forwardMostMove-1); // [HGM] race
8209 DisplayBothClocks();
8210 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8211 parseList[currentMove], _(cps->which));
8212 DisplayMoveError(buf1);
8213 DrawPosition(FALSE, boards[currentMove]);
8216 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8217 /* Program has a broken "time" command that
8218 outputs a string not ending in newline.
8224 * If chess program startup fails, exit with an error message.
8225 * Attempts to recover here are futile.
8227 if ((StrStr(message, "unknown host") != NULL)
8228 || (StrStr(message, "No remote directory") != NULL)
8229 || (StrStr(message, "not found") != NULL)
8230 || (StrStr(message, "No such file") != NULL)
8231 || (StrStr(message, "can't alloc") != NULL)
8232 || (StrStr(message, "Permission denied") != NULL)) {
8234 cps->maybeThinking = FALSE;
8235 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8236 _(cps->which), cps->program, cps->host, message);
8237 RemoveInputSource(cps->isr);
8238 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8239 if(cps == &first) appData.noChessProgram = TRUE;
8240 DisplayError(buf1, 0);
8246 * Look for hint output
8248 if (sscanf(message, "Hint: %s", buf1) == 1) {
8249 if (cps == &first && hintRequested) {
8250 hintRequested = FALSE;
8251 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8252 &fromX, &fromY, &toX, &toY, &promoChar)) {
8253 (void) CoordsToAlgebraic(boards[forwardMostMove],
8254 PosFlags(forwardMostMove),
8255 fromY, fromX, toY, toX, promoChar, buf1);
8256 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8257 DisplayInformation(buf2);
8259 /* Hint move could not be parsed!? */
8260 snprintf(buf2, sizeof(buf2),
8261 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8262 buf1, _(cps->which));
8263 DisplayError(buf2, 0);
8266 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8272 * Ignore other messages if game is not in progress
8274 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8275 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8278 * look for win, lose, draw, or draw offer
8280 if (strncmp(message, "1-0", 3) == 0) {
8281 char *p, *q, *r = "";
8282 p = strchr(message, '{');
8290 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8292 } else if (strncmp(message, "0-1", 3) == 0) {
8293 char *p, *q, *r = "";
8294 p = strchr(message, '{');
8302 /* Kludge for Arasan 4.1 bug */
8303 if (strcmp(r, "Black resigns") == 0) {
8304 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8307 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8309 } else if (strncmp(message, "1/2", 3) == 0) {
8310 char *p, *q, *r = "";
8311 p = strchr(message, '{');
8320 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8323 } else if (strncmp(message, "White resign", 12) == 0) {
8324 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8326 } else if (strncmp(message, "Black resign", 12) == 0) {
8327 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8329 } else if (strncmp(message, "White matches", 13) == 0 ||
8330 strncmp(message, "Black matches", 13) == 0 ) {
8331 /* [HGM] ignore GNUShogi noises */
8333 } else if (strncmp(message, "White", 5) == 0 &&
8334 message[5] != '(' &&
8335 StrStr(message, "Black") == NULL) {
8336 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8338 } else if (strncmp(message, "Black", 5) == 0 &&
8339 message[5] != '(') {
8340 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8342 } else if (strcmp(message, "resign") == 0 ||
8343 strcmp(message, "computer resigns") == 0) {
8345 case MachinePlaysBlack:
8346 case IcsPlayingBlack:
8347 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8349 case MachinePlaysWhite:
8350 case IcsPlayingWhite:
8351 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8353 case TwoMachinesPlay:
8354 if (cps->twoMachinesColor[0] == 'w')
8355 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8357 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8364 } else if (strncmp(message, "opponent mates", 14) == 0) {
8366 case MachinePlaysBlack:
8367 case IcsPlayingBlack:
8368 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8370 case MachinePlaysWhite:
8371 case IcsPlayingWhite:
8372 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8374 case TwoMachinesPlay:
8375 if (cps->twoMachinesColor[0] == 'w')
8376 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8378 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8385 } else if (strncmp(message, "computer mates", 14) == 0) {
8387 case MachinePlaysBlack:
8388 case IcsPlayingBlack:
8389 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8391 case MachinePlaysWhite:
8392 case IcsPlayingWhite:
8393 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8395 case TwoMachinesPlay:
8396 if (cps->twoMachinesColor[0] == 'w')
8397 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8399 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8406 } else if (strncmp(message, "checkmate", 9) == 0) {
8407 if (WhiteOnMove(forwardMostMove)) {
8408 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8410 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8413 } else if (strstr(message, "Draw") != NULL ||
8414 strstr(message, "game is a draw") != NULL) {
8415 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8417 } else if (strstr(message, "offer") != NULL &&
8418 strstr(message, "draw") != NULL) {
8420 if (appData.zippyPlay && first.initDone) {
8421 /* Relay offer to ICS */
8422 SendToICS(ics_prefix);
8423 SendToICS("draw\n");
8426 cps->offeredDraw = 2; /* valid until this engine moves twice */
8427 if (gameMode == TwoMachinesPlay) {
8428 if (cps->other->offeredDraw) {
8429 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8430 /* [HGM] in two-machine mode we delay relaying draw offer */
8431 /* until after we also have move, to see if it is really claim */
8433 } else if (gameMode == MachinePlaysWhite ||
8434 gameMode == MachinePlaysBlack) {
8435 if (userOfferedDraw) {
8436 DisplayInformation(_("Machine accepts your draw offer"));
8437 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8439 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8446 * Look for thinking output
8448 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8449 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8451 int plylev, mvleft, mvtot, curscore, time;
8452 char mvname[MOVE_LEN];
8456 int prefixHint = FALSE;
8457 mvname[0] = NULLCHAR;
8460 case MachinePlaysBlack:
8461 case IcsPlayingBlack:
8462 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8464 case MachinePlaysWhite:
8465 case IcsPlayingWhite:
8466 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8471 case IcsObserving: /* [DM] icsEngineAnalyze */
8472 if (!appData.icsEngineAnalyze) ignore = TRUE;
8474 case TwoMachinesPlay:
8475 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8485 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8487 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8488 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8490 if (plyext != ' ' && plyext != '\t') {
8494 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8495 if( cps->scoreIsAbsolute &&
8496 ( gameMode == MachinePlaysBlack ||
8497 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8498 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8499 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8500 !WhiteOnMove(currentMove)
8503 curscore = -curscore;
8507 tempStats.depth = plylev;
8508 tempStats.nodes = nodes;
8509 tempStats.time = time;
8510 tempStats.score = curscore;
8511 tempStats.got_only_move = 0;
8513 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8516 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8517 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8518 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8519 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8520 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8521 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8522 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8523 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8526 /* Buffer overflow protection */
8527 if (buf1[0] != NULLCHAR) {
8528 if (strlen(buf1) >= sizeof(tempStats.movelist)
8529 && appData.debugMode) {
8531 "PV is too long; using the first %u bytes.\n",
8532 (unsigned) sizeof(tempStats.movelist) - 1);
8535 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8537 sprintf(tempStats.movelist, " no PV\n");
8540 if (tempStats.seen_stat) {
8541 tempStats.ok_to_send = 1;
8544 if (strchr(tempStats.movelist, '(') != NULL) {
8545 tempStats.line_is_book = 1;
8546 tempStats.nr_moves = 0;
8547 tempStats.moves_left = 0;
8549 tempStats.line_is_book = 0;
8552 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8553 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8555 SendProgramStatsToFrontend( cps, &tempStats );
8558 [AS] Protect the thinkOutput buffer from overflow... this
8559 is only useful if buf1 hasn't overflowed first!
8561 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8563 (gameMode == TwoMachinesPlay ?
8564 ToUpper(cps->twoMachinesColor[0]) : ' '),
8565 ((double) curscore) / 100.0,
8566 prefixHint ? lastHint : "",
8567 prefixHint ? " " : "" );
8569 if( buf1[0] != NULLCHAR ) {
8570 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8572 if( strlen(buf1) > max_len ) {
8573 if( appData.debugMode) {
8574 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8576 buf1[max_len+1] = '\0';
8579 strcat( thinkOutput, buf1 );
8582 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8583 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8584 DisplayMove(currentMove - 1);
8588 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8589 /* crafty (9.25+) says "(only move) <move>"
8590 * if there is only 1 legal move
8592 sscanf(p, "(only move) %s", buf1);
8593 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8594 sprintf(programStats.movelist, "%s (only move)", buf1);
8595 programStats.depth = 1;
8596 programStats.nr_moves = 1;
8597 programStats.moves_left = 1;
8598 programStats.nodes = 1;
8599 programStats.time = 1;
8600 programStats.got_only_move = 1;
8602 /* Not really, but we also use this member to
8603 mean "line isn't going to change" (Crafty
8604 isn't searching, so stats won't change) */
8605 programStats.line_is_book = 1;
8607 SendProgramStatsToFrontend( cps, &programStats );
8609 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8610 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8611 DisplayMove(currentMove - 1);
8614 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8615 &time, &nodes, &plylev, &mvleft,
8616 &mvtot, mvname) >= 5) {
8617 /* The stat01: line is from Crafty (9.29+) in response
8618 to the "." command */
8619 programStats.seen_stat = 1;
8620 cps->maybeThinking = TRUE;
8622 if (programStats.got_only_move || !appData.periodicUpdates)
8625 programStats.depth = plylev;
8626 programStats.time = time;
8627 programStats.nodes = nodes;
8628 programStats.moves_left = mvleft;
8629 programStats.nr_moves = mvtot;
8630 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8631 programStats.ok_to_send = 1;
8632 programStats.movelist[0] = '\0';
8634 SendProgramStatsToFrontend( cps, &programStats );
8638 } else if (strncmp(message,"++",2) == 0) {
8639 /* Crafty 9.29+ outputs this */
8640 programStats.got_fail = 2;
8643 } else if (strncmp(message,"--",2) == 0) {
8644 /* Crafty 9.29+ outputs this */
8645 programStats.got_fail = 1;
8648 } else if (thinkOutput[0] != NULLCHAR &&
8649 strncmp(message, " ", 4) == 0) {
8650 unsigned message_len;
8653 while (*p && *p == ' ') p++;
8655 message_len = strlen( p );
8657 /* [AS] Avoid buffer overflow */
8658 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8659 strcat(thinkOutput, " ");
8660 strcat(thinkOutput, p);
8663 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8664 strcat(programStats.movelist, " ");
8665 strcat(programStats.movelist, p);
8668 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8669 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8670 DisplayMove(currentMove - 1);
8678 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8679 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8681 ChessProgramStats cpstats;
8683 if (plyext != ' ' && plyext != '\t') {
8687 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8688 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8689 curscore = -curscore;
8692 cpstats.depth = plylev;
8693 cpstats.nodes = nodes;
8694 cpstats.time = time;
8695 cpstats.score = curscore;
8696 cpstats.got_only_move = 0;
8697 cpstats.movelist[0] = '\0';
8699 if (buf1[0] != NULLCHAR) {
8700 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8703 cpstats.ok_to_send = 0;
8704 cpstats.line_is_book = 0;
8705 cpstats.nr_moves = 0;
8706 cpstats.moves_left = 0;
8708 SendProgramStatsToFrontend( cps, &cpstats );
8715 /* Parse a game score from the character string "game", and
8716 record it as the history of the current game. The game
8717 score is NOT assumed to start from the standard position.
8718 The display is not updated in any way.
8721 ParseGameHistory(game)
8725 int fromX, fromY, toX, toY, boardIndex;
8730 if (appData.debugMode)
8731 fprintf(debugFP, "Parsing game history: %s\n", game);
8733 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8734 gameInfo.site = StrSave(appData.icsHost);
8735 gameInfo.date = PGNDate();
8736 gameInfo.round = StrSave("-");
8738 /* Parse out names of players */
8739 while (*game == ' ') game++;
8741 while (*game != ' ') *p++ = *game++;
8743 gameInfo.white = StrSave(buf);
8744 while (*game == ' ') game++;
8746 while (*game != ' ' && *game != '\n') *p++ = *game++;
8748 gameInfo.black = StrSave(buf);
8751 boardIndex = blackPlaysFirst ? 1 : 0;
8754 yyboardindex = boardIndex;
8755 moveType = (ChessMove) Myylex();
8757 case IllegalMove: /* maybe suicide chess, etc. */
8758 if (appData.debugMode) {
8759 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8760 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8761 setbuf(debugFP, NULL);
8763 case WhitePromotion:
8764 case BlackPromotion:
8765 case WhiteNonPromotion:
8766 case BlackNonPromotion:
8768 case WhiteCapturesEnPassant:
8769 case BlackCapturesEnPassant:
8770 case WhiteKingSideCastle:
8771 case WhiteQueenSideCastle:
8772 case BlackKingSideCastle:
8773 case BlackQueenSideCastle:
8774 case WhiteKingSideCastleWild:
8775 case WhiteQueenSideCastleWild:
8776 case BlackKingSideCastleWild:
8777 case BlackQueenSideCastleWild:
8779 case WhiteHSideCastleFR:
8780 case WhiteASideCastleFR:
8781 case BlackHSideCastleFR:
8782 case BlackASideCastleFR:
8784 fromX = currentMoveString[0] - AAA;
8785 fromY = currentMoveString[1] - ONE;
8786 toX = currentMoveString[2] - AAA;
8787 toY = currentMoveString[3] - ONE;
8788 promoChar = currentMoveString[4];
8792 fromX = moveType == WhiteDrop ?
8793 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8794 (int) CharToPiece(ToLower(currentMoveString[0]));
8796 toX = currentMoveString[2] - AAA;
8797 toY = currentMoveString[3] - ONE;
8798 promoChar = NULLCHAR;
8802 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8803 if (appData.debugMode) {
8804 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8805 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8806 setbuf(debugFP, NULL);
8808 DisplayError(buf, 0);
8810 case ImpossibleMove:
8812 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8813 if (appData.debugMode) {
8814 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8815 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8816 setbuf(debugFP, NULL);
8818 DisplayError(buf, 0);
8821 if (boardIndex < backwardMostMove) {
8822 /* Oops, gap. How did that happen? */
8823 DisplayError(_("Gap in move list"), 0);
8826 backwardMostMove = blackPlaysFirst ? 1 : 0;
8827 if (boardIndex > forwardMostMove) {
8828 forwardMostMove = boardIndex;
8832 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8833 strcat(parseList[boardIndex-1], " ");
8834 strcat(parseList[boardIndex-1], yy_text);
8846 case GameUnfinished:
8847 if (gameMode == IcsExamining) {
8848 if (boardIndex < backwardMostMove) {
8849 /* Oops, gap. How did that happen? */
8852 backwardMostMove = blackPlaysFirst ? 1 : 0;
8855 gameInfo.result = moveType;
8856 p = strchr(yy_text, '{');
8857 if (p == NULL) p = strchr(yy_text, '(');
8860 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8862 q = strchr(p, *p == '{' ? '}' : ')');
8863 if (q != NULL) *q = NULLCHAR;
8866 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8867 gameInfo.resultDetails = StrSave(p);
8870 if (boardIndex >= forwardMostMove &&
8871 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8872 backwardMostMove = blackPlaysFirst ? 1 : 0;
8875 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8876 fromY, fromX, toY, toX, promoChar,
8877 parseList[boardIndex]);
8878 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8879 /* currentMoveString is set as a side-effect of yylex */
8880 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8881 strcat(moveList[boardIndex], "\n");
8883 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8884 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8890 if(gameInfo.variant != VariantShogi)
8891 strcat(parseList[boardIndex - 1], "+");
8895 strcat(parseList[boardIndex - 1], "#");
8902 /* Apply a move to the given board */
8904 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8905 int fromX, fromY, toX, toY;
8909 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8910 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8912 /* [HGM] compute & store e.p. status and castling rights for new position */
8913 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8915 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8916 oldEP = (signed char)board[EP_STATUS];
8917 board[EP_STATUS] = EP_NONE;
8919 if( board[toY][toX] != EmptySquare )
8920 board[EP_STATUS] = EP_CAPTURE;
8922 if (fromY == DROP_RANK) {
8924 piece = board[toY][toX] = (ChessSquare) fromX;
8928 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8929 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8930 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8932 if( board[fromY][fromX] == WhitePawn ) {
8933 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8934 board[EP_STATUS] = EP_PAWN_MOVE;
8936 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8937 gameInfo.variant != VariantBerolina || toX < fromX)
8938 board[EP_STATUS] = toX | berolina;
8939 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8940 gameInfo.variant != VariantBerolina || toX > fromX)
8941 board[EP_STATUS] = toX;
8944 if( board[fromY][fromX] == BlackPawn ) {
8945 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8946 board[EP_STATUS] = EP_PAWN_MOVE;
8947 if( toY-fromY== -2) {
8948 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8949 gameInfo.variant != VariantBerolina || toX < fromX)
8950 board[EP_STATUS] = toX | berolina;
8951 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8952 gameInfo.variant != VariantBerolina || toX > fromX)
8953 board[EP_STATUS] = toX;
8957 for(i=0; i<nrCastlingRights; i++) {
8958 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8959 board[CASTLING][i] == toX && castlingRank[i] == toY
8960 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8963 if (fromX == toX && fromY == toY) return;
8965 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8966 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8967 if(gameInfo.variant == VariantKnightmate)
8968 king += (int) WhiteUnicorn - (int) WhiteKing;
8970 /* Code added by Tord: */
8971 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8972 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8973 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8974 board[fromY][fromX] = EmptySquare;
8975 board[toY][toX] = EmptySquare;
8976 if((toX > fromX) != (piece == WhiteRook)) {
8977 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8979 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8981 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8982 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8983 board[fromY][fromX] = EmptySquare;
8984 board[toY][toX] = EmptySquare;
8985 if((toX > fromX) != (piece == BlackRook)) {
8986 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8988 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8990 /* End of code added by Tord */
8992 } else if (board[fromY][fromX] == king
8993 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8994 && toY == fromY && toX > fromX+1) {
8995 board[fromY][fromX] = EmptySquare;
8996 board[toY][toX] = king;
8997 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8998 board[fromY][BOARD_RGHT-1] = EmptySquare;
8999 } else if (board[fromY][fromX] == king
9000 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9001 && toY == fromY && toX < fromX-1) {
9002 board[fromY][fromX] = EmptySquare;
9003 board[toY][toX] = king;
9004 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9005 board[fromY][BOARD_LEFT] = EmptySquare;
9006 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9007 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9008 && toY >= BOARD_HEIGHT-promoRank
9010 /* white pawn promotion */
9011 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9012 if (board[toY][toX] == EmptySquare) {
9013 board[toY][toX] = WhiteQueen;
9015 if(gameInfo.variant==VariantBughouse ||
9016 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9017 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9018 board[fromY][fromX] = EmptySquare;
9019 } else if ((fromY == BOARD_HEIGHT-4)
9021 && gameInfo.variant != VariantXiangqi
9022 && gameInfo.variant != VariantBerolina
9023 && (board[fromY][fromX] == WhitePawn)
9024 && (board[toY][toX] == EmptySquare)) {
9025 board[fromY][fromX] = EmptySquare;
9026 board[toY][toX] = WhitePawn;
9027 captured = board[toY - 1][toX];
9028 board[toY - 1][toX] = EmptySquare;
9029 } else if ((fromY == BOARD_HEIGHT-4)
9031 && gameInfo.variant == VariantBerolina
9032 && (board[fromY][fromX] == WhitePawn)
9033 && (board[toY][toX] == EmptySquare)) {
9034 board[fromY][fromX] = EmptySquare;
9035 board[toY][toX] = WhitePawn;
9036 if(oldEP & EP_BEROLIN_A) {
9037 captured = board[fromY][fromX-1];
9038 board[fromY][fromX-1] = EmptySquare;
9039 }else{ captured = board[fromY][fromX+1];
9040 board[fromY][fromX+1] = EmptySquare;
9042 } else if (board[fromY][fromX] == king
9043 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9044 && toY == fromY && toX > fromX+1) {
9045 board[fromY][fromX] = EmptySquare;
9046 board[toY][toX] = king;
9047 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9048 board[fromY][BOARD_RGHT-1] = EmptySquare;
9049 } else if (board[fromY][fromX] == king
9050 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9051 && toY == fromY && toX < fromX-1) {
9052 board[fromY][fromX] = EmptySquare;
9053 board[toY][toX] = king;
9054 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9055 board[fromY][BOARD_LEFT] = EmptySquare;
9056 } else if (fromY == 7 && fromX == 3
9057 && board[fromY][fromX] == BlackKing
9058 && toY == 7 && toX == 5) {
9059 board[fromY][fromX] = EmptySquare;
9060 board[toY][toX] = BlackKing;
9061 board[fromY][7] = EmptySquare;
9062 board[toY][4] = BlackRook;
9063 } else if (fromY == 7 && fromX == 3
9064 && board[fromY][fromX] == BlackKing
9065 && toY == 7 && toX == 1) {
9066 board[fromY][fromX] = EmptySquare;
9067 board[toY][toX] = BlackKing;
9068 board[fromY][0] = EmptySquare;
9069 board[toY][2] = BlackRook;
9070 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9071 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9074 /* black pawn promotion */
9075 board[toY][toX] = CharToPiece(ToLower(promoChar));
9076 if (board[toY][toX] == EmptySquare) {
9077 board[toY][toX] = BlackQueen;
9079 if(gameInfo.variant==VariantBughouse ||
9080 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9081 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9082 board[fromY][fromX] = EmptySquare;
9083 } else if ((fromY == 3)
9085 && gameInfo.variant != VariantXiangqi
9086 && gameInfo.variant != VariantBerolina
9087 && (board[fromY][fromX] == BlackPawn)
9088 && (board[toY][toX] == EmptySquare)) {
9089 board[fromY][fromX] = EmptySquare;
9090 board[toY][toX] = BlackPawn;
9091 captured = board[toY + 1][toX];
9092 board[toY + 1][toX] = EmptySquare;
9093 } else if ((fromY == 3)
9095 && gameInfo.variant == VariantBerolina
9096 && (board[fromY][fromX] == BlackPawn)
9097 && (board[toY][toX] == EmptySquare)) {
9098 board[fromY][fromX] = EmptySquare;
9099 board[toY][toX] = BlackPawn;
9100 if(oldEP & EP_BEROLIN_A) {
9101 captured = board[fromY][fromX-1];
9102 board[fromY][fromX-1] = EmptySquare;
9103 }else{ captured = board[fromY][fromX+1];
9104 board[fromY][fromX+1] = EmptySquare;
9107 board[toY][toX] = board[fromY][fromX];
9108 board[fromY][fromX] = EmptySquare;
9112 if (gameInfo.holdingsWidth != 0) {
9114 /* !!A lot more code needs to be written to support holdings */
9115 /* [HGM] OK, so I have written it. Holdings are stored in the */
9116 /* penultimate board files, so they are automaticlly stored */
9117 /* in the game history. */
9118 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9119 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9120 /* Delete from holdings, by decreasing count */
9121 /* and erasing image if necessary */
9122 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9123 if(p < (int) BlackPawn) { /* white drop */
9124 p -= (int)WhitePawn;
9125 p = PieceToNumber((ChessSquare)p);
9126 if(p >= gameInfo.holdingsSize) p = 0;
9127 if(--board[p][BOARD_WIDTH-2] <= 0)
9128 board[p][BOARD_WIDTH-1] = EmptySquare;
9129 if((int)board[p][BOARD_WIDTH-2] < 0)
9130 board[p][BOARD_WIDTH-2] = 0;
9131 } else { /* black drop */
9132 p -= (int)BlackPawn;
9133 p = PieceToNumber((ChessSquare)p);
9134 if(p >= gameInfo.holdingsSize) p = 0;
9135 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9136 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9137 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9138 board[BOARD_HEIGHT-1-p][1] = 0;
9141 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9142 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9143 /* [HGM] holdings: Add to holdings, if holdings exist */
9144 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9145 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9146 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9149 if (p >= (int) BlackPawn) {
9150 p -= (int)BlackPawn;
9151 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9152 /* in Shogi restore piece to its original first */
9153 captured = (ChessSquare) (DEMOTED captured);
9156 p = PieceToNumber((ChessSquare)p);
9157 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9158 board[p][BOARD_WIDTH-2]++;
9159 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9161 p -= (int)WhitePawn;
9162 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9163 captured = (ChessSquare) (DEMOTED captured);
9166 p = PieceToNumber((ChessSquare)p);
9167 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9168 board[BOARD_HEIGHT-1-p][1]++;
9169 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9172 } else if (gameInfo.variant == VariantAtomic) {
9173 if (captured != EmptySquare) {
9175 for (y = toY-1; y <= toY+1; y++) {
9176 for (x = toX-1; x <= toX+1; x++) {
9177 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9178 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9179 board[y][x] = EmptySquare;
9183 board[toY][toX] = EmptySquare;
9186 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9187 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9189 if(promoChar == '+') {
9190 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9191 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9192 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9193 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9195 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9196 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9197 // [HGM] superchess: take promotion piece out of holdings
9198 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9199 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9200 if(!--board[k][BOARD_WIDTH-2])
9201 board[k][BOARD_WIDTH-1] = EmptySquare;
9203 if(!--board[BOARD_HEIGHT-1-k][1])
9204 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9210 /* Updates forwardMostMove */
9212 MakeMove(fromX, fromY, toX, toY, promoChar)
9213 int fromX, fromY, toX, toY;
9216 // forwardMostMove++; // [HGM] bare: moved downstream
9218 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9219 int timeLeft; static int lastLoadFlag=0; int king, piece;
9220 piece = boards[forwardMostMove][fromY][fromX];
9221 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9222 if(gameInfo.variant == VariantKnightmate)
9223 king += (int) WhiteUnicorn - (int) WhiteKing;
9224 if(forwardMostMove == 0) {
9226 fprintf(serverMoves, "%s;", second.tidy);
9227 fprintf(serverMoves, "%s;", first.tidy);
9228 if(!blackPlaysFirst)
9229 fprintf(serverMoves, "%s;", second.tidy);
9230 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9231 lastLoadFlag = loadFlag;
9233 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9234 // print castling suffix
9235 if( toY == fromY && piece == king ) {
9237 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9239 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9242 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9243 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9244 boards[forwardMostMove][toY][toX] == EmptySquare
9245 && fromX != toX && fromY != toY)
9246 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9248 if(promoChar != NULLCHAR)
9249 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9251 fprintf(serverMoves, "/%d/%d",
9252 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9253 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9254 else timeLeft = blackTimeRemaining/1000;
9255 fprintf(serverMoves, "/%d", timeLeft);
9257 fflush(serverMoves);
9260 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9261 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9265 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9266 if (commentList[forwardMostMove+1] != NULL) {
9267 free(commentList[forwardMostMove+1]);
9268 commentList[forwardMostMove+1] = NULL;
9270 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9271 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9272 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9273 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9274 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9275 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9276 gameInfo.result = GameUnfinished;
9277 if (gameInfo.resultDetails != NULL) {
9278 free(gameInfo.resultDetails);
9279 gameInfo.resultDetails = NULL;
9281 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9282 moveList[forwardMostMove - 1]);
9283 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9284 PosFlags(forwardMostMove - 1),
9285 fromY, fromX, toY, toX, promoChar,
9286 parseList[forwardMostMove - 1]);
9287 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9293 if(gameInfo.variant != VariantShogi)
9294 strcat(parseList[forwardMostMove - 1], "+");
9298 strcat(parseList[forwardMostMove - 1], "#");
9301 if (appData.debugMode) {
9302 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9307 /* Updates currentMove if not pausing */
9309 ShowMove(fromX, fromY, toX, toY)
9311 int instant = (gameMode == PlayFromGameFile) ?
9312 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9313 if(appData.noGUI) return;
9314 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9316 if (forwardMostMove == currentMove + 1) {
9317 AnimateMove(boards[forwardMostMove - 1],
9318 fromX, fromY, toX, toY);
9320 if (appData.highlightLastMove) {
9321 SetHighlights(fromX, fromY, toX, toY);
9324 currentMove = forwardMostMove;
9327 if (instant) return;
9329 DisplayMove(currentMove - 1);
9330 DrawPosition(FALSE, boards[currentMove]);
9331 DisplayBothClocks();
9332 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9333 DisplayBook(currentMove);
9336 void SendEgtPath(ChessProgramState *cps)
9337 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9338 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9340 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9343 char c, *q = name+1, *r, *s;
9345 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9346 while(*p && *p != ',') *q++ = *p++;
9348 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9349 strcmp(name, ",nalimov:") == 0 ) {
9350 // take nalimov path from the menu-changeable option first, if it is defined
9351 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9352 SendToProgram(buf,cps); // send egtbpath command for nalimov
9354 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9355 (s = StrStr(appData.egtFormats, name)) != NULL) {
9356 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9357 s = r = StrStr(s, ":") + 1; // beginning of path info
9358 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9359 c = *r; *r = 0; // temporarily null-terminate path info
9360 *--q = 0; // strip of trailig ':' from name
9361 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9363 SendToProgram(buf,cps); // send egtbpath command for this format
9365 if(*p == ',') p++; // read away comma to position for next format name
9370 InitChessProgram(cps, setup)
9371 ChessProgramState *cps;
9372 int setup; /* [HGM] needed to setup FRC opening position */
9374 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9375 if (appData.noChessProgram) return;
9376 hintRequested = FALSE;
9377 bookRequested = FALSE;
9379 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9380 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9381 if(cps->memSize) { /* [HGM] memory */
9382 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9383 SendToProgram(buf, cps);
9385 SendEgtPath(cps); /* [HGM] EGT */
9386 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9387 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9388 SendToProgram(buf, cps);
9391 SendToProgram(cps->initString, cps);
9392 if (gameInfo.variant != VariantNormal &&
9393 gameInfo.variant != VariantLoadable
9394 /* [HGM] also send variant if board size non-standard */
9395 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9397 char *v = VariantName(gameInfo.variant);
9398 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9399 /* [HGM] in protocol 1 we have to assume all variants valid */
9400 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9401 DisplayFatalError(buf, 0, 1);
9405 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9406 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9407 if( gameInfo.variant == VariantXiangqi )
9408 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9409 if( gameInfo.variant == VariantShogi )
9410 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9411 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9412 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9413 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9414 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9415 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9416 if( gameInfo.variant == VariantCourier )
9417 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9418 if( gameInfo.variant == VariantSuper )
9419 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9420 if( gameInfo.variant == VariantGreat )
9421 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9422 if( gameInfo.variant == VariantSChess )
9423 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9426 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9427 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9428 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9429 if(StrStr(cps->variants, b) == NULL) {
9430 // specific sized variant not known, check if general sizing allowed
9431 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9432 if(StrStr(cps->variants, "boardsize") == NULL) {
9433 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9434 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9435 DisplayFatalError(buf, 0, 1);
9438 /* [HGM] here we really should compare with the maximum supported board size */
9441 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9442 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9443 SendToProgram(buf, cps);
9445 currentlyInitializedVariant = gameInfo.variant;
9447 /* [HGM] send opening position in FRC to first engine */
9449 SendToProgram("force\n", cps);
9451 /* engine is now in force mode! Set flag to wake it up after first move. */
9452 setboardSpoiledMachineBlack = 1;
9456 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9457 SendToProgram(buf, cps);
9459 cps->maybeThinking = FALSE;
9460 cps->offeredDraw = 0;
9461 if (!appData.icsActive) {
9462 SendTimeControl(cps, movesPerSession, timeControl,
9463 timeIncrement, appData.searchDepth,
9466 if (appData.showThinking
9467 // [HGM] thinking: four options require thinking output to be sent
9468 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9470 SendToProgram("post\n", cps);
9472 SendToProgram("hard\n", cps);
9473 if (!appData.ponderNextMove) {
9474 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9475 it without being sure what state we are in first. "hard"
9476 is not a toggle, so that one is OK.
9478 SendToProgram("easy\n", cps);
9481 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9482 SendToProgram(buf, cps);
9484 cps->initDone = TRUE;
9489 StartChessProgram(cps)
9490 ChessProgramState *cps;
9495 if (appData.noChessProgram) return;
9496 cps->initDone = FALSE;
9498 if (strcmp(cps->host, "localhost") == 0) {
9499 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9500 } else if (*appData.remoteShell == NULLCHAR) {
9501 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9503 if (*appData.remoteUser == NULLCHAR) {
9504 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9507 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9508 cps->host, appData.remoteUser, cps->program);
9510 err = StartChildProcess(buf, "", &cps->pr);
9514 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9515 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9516 if(cps != &first) return;
9517 appData.noChessProgram = TRUE;
9520 // DisplayFatalError(buf, err, 1);
9521 // cps->pr = NoProc;
9526 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9527 if (cps->protocolVersion > 1) {
9528 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9529 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9530 cps->comboCnt = 0; // and values of combo boxes
9531 SendToProgram(buf, cps);
9533 SendToProgram("xboard\n", cps);
9538 TwoMachinesEventIfReady P((void))
9540 static int curMess = 0;
9541 if (first.lastPing != first.lastPong) {
9542 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9543 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9546 if (second.lastPing != second.lastPong) {
9547 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9548 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9551 DisplayMessage("", ""); curMess = 0;
9557 CountPlayers(char *p)
9560 while(p = strchr(p, '\n')) p++, n++; // count participants
9565 WriteTourneyFile(char *results)
9566 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9567 FILE *f = fopen(appData.tourneyFile, "w");
9568 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9569 // create a file with tournament description
9570 fprintf(f, "-participants {%s}\n", appData.participants);
9571 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9572 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9573 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9574 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9575 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9576 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9577 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9578 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9579 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9580 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9581 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9583 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9585 fprintf(f, "-mps %d\n", appData.movesPerSession);
9586 fprintf(f, "-tc %s\n", appData.timeControl);
9587 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9589 fprintf(f, "-results \"%s\"\n", results);
9595 CreateTourney(char *name)
9598 if(name[0] == NULLCHAR) {
9599 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9602 f = fopen(appData.tourneyFile, "r");
9603 if(f) { // file exists
9604 ParseArgsFromFile(f); // parse it
9606 if(CountPlayers(appData.participants) < appData.tourneyType + (!appData.tourneyType) + 1) {
9607 DisplayError(_("Not enough participants"), 0);
9610 if((f = WriteTourneyFile("")) == NULL) return 0;
9613 appData.noChessProgram = FALSE;
9614 appData.clockMode = TRUE;
9619 #define MAXENGINES 1000
9620 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9622 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9624 char buf[MSG_SIZ], *p, *q;
9628 while(*p && *p != '\n') *q++ = *p++;
9630 if(engineList[i]) free(engineList[i]);
9631 engineList[i] = strdup(buf);
9633 TidyProgramName(engineList[i], "localhost", buf);
9634 if(engineMnemonic[i]) free(engineMnemonic[i]);
9635 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9637 sscanf(q + 8, "%s", buf + strlen(buf));
9640 engineMnemonic[i] = strdup(buf);
9642 if(i > MAXENGINES - 2) break;
9644 engineList[i] = NULL;
9647 // following implemented as macro to avoid type limitations
9648 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9650 void SwapEngines(int n)
9651 { // swap settings for first engine and other engine (so far only some selected options)
9656 SWAP(chessProgram, p)
9658 SWAP(hasOwnBookUCI, h)
9659 SWAP(protocolVersion, h)
9661 SWAP(scoreIsAbsolute, h)
9668 SetPlayer(int player)
9669 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9671 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9672 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9673 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9674 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9676 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9677 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9678 ParseArgsFromString(buf);
9684 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9685 { // determine players from game number
9686 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9688 if(appData.tourneyType == 0) {
9689 roundsPerCycle = (nPlayers - 1) | 1;
9690 pairingsPerRound = nPlayers / 2;
9691 } else if(appData.tourneyType > 0) {
9692 roundsPerCycle = nPlayers - appData.tourneyType;
9693 pairingsPerRound = appData.tourneyType;
9695 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9696 gamesPerCycle = gamesPerRound * roundsPerCycle;
9697 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9698 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9699 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9700 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9701 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9702 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9704 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9705 if(appData.roundSync) *syncInterval = gamesPerRound;
9707 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9709 if(appData.tourneyType == 0) {
9710 if(curPairing == (nPlayers-1)/2 ) {
9711 *whitePlayer = curRound;
9712 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9714 *whitePlayer = curRound - pairingsPerRound + curPairing;
9715 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9716 *blackPlayer = curRound + pairingsPerRound - curPairing;
9717 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9719 } else if(appData.tourneyType > 0) {
9720 *whitePlayer = curPairing;
9721 *blackPlayer = curRound + appData.tourneyType;
9724 // take care of white/black alternation per round.
9725 // For cycles and games this is already taken care of by default, derived from matchGame!
9726 return curRound & 1;
9730 NextTourneyGame(int nr, int *swapColors)
9731 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9733 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9735 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9736 tf = fopen(appData.tourneyFile, "r");
9737 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9738 ParseArgsFromFile(tf); fclose(tf);
9739 InitTimeControls(); // TC might be altered from tourney file
9741 nPlayers = CountPlayers(appData.participants); // count participants
9742 if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9743 if(nr>=0 && !pairingReceived) {
9745 if(pairing.pr == NoProc) StartChessProgram(&pairing);
9746 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9747 SendToProgram(buf, &pairing);
9748 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9749 SendToProgram(buf, &pairing);
9750 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9752 pairingReceived = 0; // ... so we continue here
9753 syncInterval = nPlayers/2; *swapColors = 0;
9754 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9755 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9756 matchGame = 1; roundNr = nr / syncInterval + 1;
9758 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9761 p = q = appData.results;
9762 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9763 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9764 DisplayMessage(_("Waiting for other game(s)"),"");
9765 waitingForGame = TRUE;
9766 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9769 waitingForGame = FALSE;
9772 if(first.pr != NoProc) return 1; // engines already loaded
9774 // redefine engines, engine dir, etc.
9775 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9776 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9778 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9779 SwapEngines(1); // and make that valid for second engine by swapping
9780 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9781 InitEngine(&second, 1);
9782 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9788 { // performs game initialization that does not invoke engines, and then tries to start the game
9789 int firstWhite, swapColors = 0;
9790 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9791 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9792 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9793 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9794 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9795 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9796 Reset(FALSE, first.pr != NoProc);
9797 appData.noChessProgram = FALSE;
9798 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9802 void UserAdjudicationEvent( int result )
9804 ChessMove gameResult = GameIsDrawn;
9807 gameResult = WhiteWins;
9809 else if( result < 0 ) {
9810 gameResult = BlackWins;
9813 if( gameMode == TwoMachinesPlay ) {
9814 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9819 // [HGM] save: calculate checksum of game to make games easily identifiable
9820 int StringCheckSum(char *s)
9823 if(s==NULL) return 0;
9824 while(*s) i = i*259 + *s++;
9831 for(i=backwardMostMove; i<forwardMostMove; i++) {
9832 sum += pvInfoList[i].depth;
9833 sum += StringCheckSum(parseList[i]);
9834 sum += StringCheckSum(commentList[i]);
9837 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9838 return sum + StringCheckSum(commentList[i]);
9839 } // end of save patch
9842 GameEnds(result, resultDetails, whosays)
9844 char *resultDetails;
9847 GameMode nextGameMode;
9849 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9851 if(endingGame) return; /* [HGM] crash: forbid recursion */
9853 if(twoBoards) { // [HGM] dual: switch back to one board
9854 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9855 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9857 if (appData.debugMode) {
9858 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9859 result, resultDetails ? resultDetails : "(null)", whosays);
9862 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9864 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9865 /* If we are playing on ICS, the server decides when the
9866 game is over, but the engine can offer to draw, claim
9870 if (appData.zippyPlay && first.initDone) {
9871 if (result == GameIsDrawn) {
9872 /* In case draw still needs to be claimed */
9873 SendToICS(ics_prefix);
9874 SendToICS("draw\n");
9875 } else if (StrCaseStr(resultDetails, "resign")) {
9876 SendToICS(ics_prefix);
9877 SendToICS("resign\n");
9881 endingGame = 0; /* [HGM] crash */
9885 /* If we're loading the game from a file, stop */
9886 if (whosays == GE_FILE) {
9887 (void) StopLoadGameTimer();
9891 /* Cancel draw offers */
9892 first.offeredDraw = second.offeredDraw = 0;
9894 /* If this is an ICS game, only ICS can really say it's done;
9895 if not, anyone can. */
9896 isIcsGame = (gameMode == IcsPlayingWhite ||
9897 gameMode == IcsPlayingBlack ||
9898 gameMode == IcsObserving ||
9899 gameMode == IcsExamining);
9901 if (!isIcsGame || whosays == GE_ICS) {
9902 /* OK -- not an ICS game, or ICS said it was done */
9904 if (!isIcsGame && !appData.noChessProgram)
9905 SetUserThinkingEnables();
9907 /* [HGM] if a machine claims the game end we verify this claim */
9908 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9909 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9911 ChessMove trueResult = (ChessMove) -1;
9913 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9914 first.twoMachinesColor[0] :
9915 second.twoMachinesColor[0] ;
9917 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9918 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9919 /* [HGM] verify: engine mate claims accepted if they were flagged */
9920 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9922 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9923 /* [HGM] verify: engine mate claims accepted if they were flagged */
9924 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9926 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9927 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9930 // now verify win claims, but not in drop games, as we don't understand those yet
9931 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9932 || gameInfo.variant == VariantGreat) &&
9933 (result == WhiteWins && claimer == 'w' ||
9934 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9935 if (appData.debugMode) {
9936 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9937 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9939 if(result != trueResult) {
9940 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9941 result = claimer == 'w' ? BlackWins : WhiteWins;
9942 resultDetails = buf;
9945 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9946 && (forwardMostMove <= backwardMostMove ||
9947 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9948 (claimer=='b')==(forwardMostMove&1))
9950 /* [HGM] verify: draws that were not flagged are false claims */
9951 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9952 result = claimer == 'w' ? BlackWins : WhiteWins;
9953 resultDetails = buf;
9955 /* (Claiming a loss is accepted no questions asked!) */
9957 /* [HGM] bare: don't allow bare King to win */
9958 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9959 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9960 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9961 && result != GameIsDrawn)
9962 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9963 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9964 int p = (signed char)boards[forwardMostMove][i][j] - color;
9965 if(p >= 0 && p <= (int)WhiteKing) k++;
9967 if (appData.debugMode) {
9968 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9969 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9972 result = GameIsDrawn;
9973 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9974 resultDetails = buf;
9980 if(serverMoves != NULL && !loadFlag) { char c = '=';
9981 if(result==WhiteWins) c = '+';
9982 if(result==BlackWins) c = '-';
9983 if(resultDetails != NULL)
9984 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9986 if (resultDetails != NULL) {
9987 gameInfo.result = result;
9988 gameInfo.resultDetails = StrSave(resultDetails);
9990 /* display last move only if game was not loaded from file */
9991 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9992 DisplayMove(currentMove - 1);
9994 if (forwardMostMove != 0) {
9995 if (gameMode != PlayFromGameFile && gameMode != EditGame
9996 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9998 if (*appData.saveGameFile != NULLCHAR) {
9999 SaveGameToFile(appData.saveGameFile, TRUE);
10000 } else if (appData.autoSaveGames) {
10003 if (*appData.savePositionFile != NULLCHAR) {
10004 SavePositionToFile(appData.savePositionFile);
10009 /* Tell program how game ended in case it is learning */
10010 /* [HGM] Moved this to after saving the PGN, just in case */
10011 /* engine died and we got here through time loss. In that */
10012 /* case we will get a fatal error writing the pipe, which */
10013 /* would otherwise lose us the PGN. */
10014 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10015 /* output during GameEnds should never be fatal anymore */
10016 if (gameMode == MachinePlaysWhite ||
10017 gameMode == MachinePlaysBlack ||
10018 gameMode == TwoMachinesPlay ||
10019 gameMode == IcsPlayingWhite ||
10020 gameMode == IcsPlayingBlack ||
10021 gameMode == BeginningOfGame) {
10023 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10025 if (first.pr != NoProc) {
10026 SendToProgram(buf, &first);
10028 if (second.pr != NoProc &&
10029 gameMode == TwoMachinesPlay) {
10030 SendToProgram(buf, &second);
10035 if (appData.icsActive) {
10036 if (appData.quietPlay &&
10037 (gameMode == IcsPlayingWhite ||
10038 gameMode == IcsPlayingBlack)) {
10039 SendToICS(ics_prefix);
10040 SendToICS("set shout 1\n");
10042 nextGameMode = IcsIdle;
10043 ics_user_moved = FALSE;
10044 /* clean up premove. It's ugly when the game has ended and the
10045 * premove highlights are still on the board.
10048 gotPremove = FALSE;
10049 ClearPremoveHighlights();
10050 DrawPosition(FALSE, boards[currentMove]);
10052 if (whosays == GE_ICS) {
10055 if (gameMode == IcsPlayingWhite)
10057 else if(gameMode == IcsPlayingBlack)
10058 PlayIcsLossSound();
10061 if (gameMode == IcsPlayingBlack)
10063 else if(gameMode == IcsPlayingWhite)
10064 PlayIcsLossSound();
10067 PlayIcsDrawSound();
10070 PlayIcsUnfinishedSound();
10073 } else if (gameMode == EditGame ||
10074 gameMode == PlayFromGameFile ||
10075 gameMode == AnalyzeMode ||
10076 gameMode == AnalyzeFile) {
10077 nextGameMode = gameMode;
10079 nextGameMode = EndOfGame;
10084 nextGameMode = gameMode;
10087 if (appData.noChessProgram) {
10088 gameMode = nextGameMode;
10090 endingGame = 0; /* [HGM] crash */
10095 /* Put first chess program into idle state */
10096 if (first.pr != NoProc &&
10097 (gameMode == MachinePlaysWhite ||
10098 gameMode == MachinePlaysBlack ||
10099 gameMode == TwoMachinesPlay ||
10100 gameMode == IcsPlayingWhite ||
10101 gameMode == IcsPlayingBlack ||
10102 gameMode == BeginningOfGame)) {
10103 SendToProgram("force\n", &first);
10104 if (first.usePing) {
10106 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10107 SendToProgram(buf, &first);
10110 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10111 /* Kill off first chess program */
10112 if (first.isr != NULL)
10113 RemoveInputSource(first.isr);
10116 if (first.pr != NoProc) {
10118 DoSleep( appData.delayBeforeQuit );
10119 SendToProgram("quit\n", &first);
10120 DoSleep( appData.delayAfterQuit );
10121 DestroyChildProcess(first.pr, first.useSigterm);
10125 if (second.reuse) {
10126 /* Put second chess program into idle state */
10127 if (second.pr != NoProc &&
10128 gameMode == TwoMachinesPlay) {
10129 SendToProgram("force\n", &second);
10130 if (second.usePing) {
10132 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10133 SendToProgram(buf, &second);
10136 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10137 /* Kill off second chess program */
10138 if (second.isr != NULL)
10139 RemoveInputSource(second.isr);
10142 if (second.pr != NoProc) {
10143 DoSleep( appData.delayBeforeQuit );
10144 SendToProgram("quit\n", &second);
10145 DoSleep( appData.delayAfterQuit );
10146 DestroyChildProcess(second.pr, second.useSigterm);
10148 second.pr = NoProc;
10151 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10152 char resChar = '=';
10156 if (first.twoMachinesColor[0] == 'w') {
10159 second.matchWins++;
10164 if (first.twoMachinesColor[0] == 'b') {
10167 second.matchWins++;
10170 case GameUnfinished:
10176 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10177 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10178 ReserveGame(nextGame, resChar); // sets nextGame
10179 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10180 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10182 if (nextGame <= appData.matchGames && !abortMatch) {
10183 gameMode = nextGameMode;
10184 matchGame = nextGame; // this will be overruled in tourney mode!
10185 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10186 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10187 endingGame = 0; /* [HGM] crash */
10190 gameMode = nextGameMode;
10191 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10192 first.tidy, second.tidy,
10193 first.matchWins, second.matchWins,
10194 appData.matchGames - (first.matchWins + second.matchWins));
10195 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10196 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10197 first.twoMachinesColor = "black\n";
10198 second.twoMachinesColor = "white\n";
10200 first.twoMachinesColor = "white\n";
10201 second.twoMachinesColor = "black\n";
10205 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10206 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10208 gameMode = nextGameMode;
10210 endingGame = 0; /* [HGM] crash */
10211 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10212 if(matchMode == TRUE) { // match through command line: exit with or without popup
10214 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10216 } else DisplayFatalError(buf, 0, 0);
10217 } else { // match through menu; just stop, with or without popup
10218 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10220 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10221 } else DisplayNote(buf);
10223 if(ranking) free(ranking);
10227 /* Assumes program was just initialized (initString sent).
10228 Leaves program in force mode. */
10230 FeedMovesToProgram(cps, upto)
10231 ChessProgramState *cps;
10236 if (appData.debugMode)
10237 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10238 startedFromSetupPosition ? "position and " : "",
10239 backwardMostMove, upto, cps->which);
10240 if(currentlyInitializedVariant != gameInfo.variant) {
10242 // [HGM] variantswitch: make engine aware of new variant
10243 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10244 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10245 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10246 SendToProgram(buf, cps);
10247 currentlyInitializedVariant = gameInfo.variant;
10249 SendToProgram("force\n", cps);
10250 if (startedFromSetupPosition) {
10251 SendBoard(cps, backwardMostMove);
10252 if (appData.debugMode) {
10253 fprintf(debugFP, "feedMoves\n");
10256 for (i = backwardMostMove; i < upto; i++) {
10257 SendMoveToProgram(i, cps);
10263 ResurrectChessProgram()
10265 /* The chess program may have exited.
10266 If so, restart it and feed it all the moves made so far. */
10267 static int doInit = 0;
10269 if (appData.noChessProgram) return 1;
10271 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10272 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10273 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10274 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10276 if (first.pr != NoProc) return 1;
10277 StartChessProgram(&first);
10279 InitChessProgram(&first, FALSE);
10280 FeedMovesToProgram(&first, currentMove);
10282 if (!first.sendTime) {
10283 /* can't tell gnuchess what its clock should read,
10284 so we bow to its notion. */
10286 timeRemaining[0][currentMove] = whiteTimeRemaining;
10287 timeRemaining[1][currentMove] = blackTimeRemaining;
10290 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10291 appData.icsEngineAnalyze) && first.analysisSupport) {
10292 SendToProgram("analyze\n", &first);
10293 first.analyzing = TRUE;
10299 * Button procedures
10302 Reset(redraw, init)
10307 if (appData.debugMode) {
10308 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10309 redraw, init, gameMode);
10311 CleanupTail(); // [HGM] vari: delete any stored variations
10312 pausing = pauseExamInvalid = FALSE;
10313 startedFromSetupPosition = blackPlaysFirst = FALSE;
10315 whiteFlag = blackFlag = FALSE;
10316 userOfferedDraw = FALSE;
10317 hintRequested = bookRequested = FALSE;
10318 first.maybeThinking = FALSE;
10319 second.maybeThinking = FALSE;
10320 first.bookSuspend = FALSE; // [HGM] book
10321 second.bookSuspend = FALSE;
10322 thinkOutput[0] = NULLCHAR;
10323 lastHint[0] = NULLCHAR;
10324 ClearGameInfo(&gameInfo);
10325 gameInfo.variant = StringToVariant(appData.variant);
10326 ics_user_moved = ics_clock_paused = FALSE;
10327 ics_getting_history = H_FALSE;
10329 white_holding[0] = black_holding[0] = NULLCHAR;
10330 ClearProgramStats();
10331 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10335 flipView = appData.flipView;
10336 ClearPremoveHighlights();
10337 gotPremove = FALSE;
10338 alarmSounded = FALSE;
10340 GameEnds(EndOfFile, NULL, GE_PLAYER);
10341 if(appData.serverMovesName != NULL) {
10342 /* [HGM] prepare to make moves file for broadcasting */
10343 clock_t t = clock();
10344 if(serverMoves != NULL) fclose(serverMoves);
10345 serverMoves = fopen(appData.serverMovesName, "r");
10346 if(serverMoves != NULL) {
10347 fclose(serverMoves);
10348 /* delay 15 sec before overwriting, so all clients can see end */
10349 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10351 serverMoves = fopen(appData.serverMovesName, "w");
10355 gameMode = BeginningOfGame;
10357 if(appData.icsActive) gameInfo.variant = VariantNormal;
10358 currentMove = forwardMostMove = backwardMostMove = 0;
10359 InitPosition(redraw);
10360 for (i = 0; i < MAX_MOVES; i++) {
10361 if (commentList[i] != NULL) {
10362 free(commentList[i]);
10363 commentList[i] = NULL;
10367 timeRemaining[0][0] = whiteTimeRemaining;
10368 timeRemaining[1][0] = blackTimeRemaining;
10370 if (first.pr == NULL) {
10371 StartChessProgram(&first);
10374 InitChessProgram(&first, startedFromSetupPosition);
10377 DisplayMessage("", "");
10378 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10379 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10386 if (!AutoPlayOneMove())
10388 if (matchMode || appData.timeDelay == 0)
10390 if (appData.timeDelay < 0)
10392 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10401 int fromX, fromY, toX, toY;
10403 if (appData.debugMode) {
10404 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10407 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10410 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10411 pvInfoList[currentMove].depth = programStats.depth;
10412 pvInfoList[currentMove].score = programStats.score;
10413 pvInfoList[currentMove].time = 0;
10414 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10417 if (currentMove >= forwardMostMove) {
10418 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10419 gameMode = EditGame;
10422 /* [AS] Clear current move marker at the end of a game */
10423 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10428 toX = moveList[currentMove][2] - AAA;
10429 toY = moveList[currentMove][3] - ONE;
10431 if (moveList[currentMove][1] == '@') {
10432 if (appData.highlightLastMove) {
10433 SetHighlights(-1, -1, toX, toY);
10436 fromX = moveList[currentMove][0] - AAA;
10437 fromY = moveList[currentMove][1] - ONE;
10439 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10441 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10443 if (appData.highlightLastMove) {
10444 SetHighlights(fromX, fromY, toX, toY);
10447 DisplayMove(currentMove);
10448 SendMoveToProgram(currentMove++, &first);
10449 DisplayBothClocks();
10450 DrawPosition(FALSE, boards[currentMove]);
10451 // [HGM] PV info: always display, routine tests if empty
10452 DisplayComment(currentMove - 1, commentList[currentMove]);
10458 LoadGameOneMove(readAhead)
10459 ChessMove readAhead;
10461 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10462 char promoChar = NULLCHAR;
10463 ChessMove moveType;
10464 char move[MSG_SIZ];
10467 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10468 gameMode != AnalyzeMode && gameMode != Training) {
10473 yyboardindex = forwardMostMove;
10474 if (readAhead != EndOfFile) {
10475 moveType = readAhead;
10477 if (gameFileFP == NULL)
10479 moveType = (ChessMove) Myylex();
10483 switch (moveType) {
10485 if (appData.debugMode)
10486 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10489 /* append the comment but don't display it */
10490 AppendComment(currentMove, p, FALSE);
10493 case WhiteCapturesEnPassant:
10494 case BlackCapturesEnPassant:
10495 case WhitePromotion:
10496 case BlackPromotion:
10497 case WhiteNonPromotion:
10498 case BlackNonPromotion:
10500 case WhiteKingSideCastle:
10501 case WhiteQueenSideCastle:
10502 case BlackKingSideCastle:
10503 case BlackQueenSideCastle:
10504 case WhiteKingSideCastleWild:
10505 case WhiteQueenSideCastleWild:
10506 case BlackKingSideCastleWild:
10507 case BlackQueenSideCastleWild:
10509 case WhiteHSideCastleFR:
10510 case WhiteASideCastleFR:
10511 case BlackHSideCastleFR:
10512 case BlackASideCastleFR:
10514 if (appData.debugMode)
10515 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10516 fromX = currentMoveString[0] - AAA;
10517 fromY = currentMoveString[1] - ONE;
10518 toX = currentMoveString[2] - AAA;
10519 toY = currentMoveString[3] - ONE;
10520 promoChar = currentMoveString[4];
10525 if (appData.debugMode)
10526 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10527 fromX = moveType == WhiteDrop ?
10528 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10529 (int) CharToPiece(ToLower(currentMoveString[0]));
10531 toX = currentMoveString[2] - AAA;
10532 toY = currentMoveString[3] - ONE;
10538 case GameUnfinished:
10539 if (appData.debugMode)
10540 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10541 p = strchr(yy_text, '{');
10542 if (p == NULL) p = strchr(yy_text, '(');
10545 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10547 q = strchr(p, *p == '{' ? '}' : ')');
10548 if (q != NULL) *q = NULLCHAR;
10551 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10552 GameEnds(moveType, p, GE_FILE);
10554 if (cmailMsgLoaded) {
10556 flipView = WhiteOnMove(currentMove);
10557 if (moveType == GameUnfinished) flipView = !flipView;
10558 if (appData.debugMode)
10559 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10564 if (appData.debugMode)
10565 fprintf(debugFP, "Parser hit end of file\n");
10566 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10572 if (WhiteOnMove(currentMove)) {
10573 GameEnds(BlackWins, "Black mates", GE_FILE);
10575 GameEnds(WhiteWins, "White mates", GE_FILE);
10579 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10585 case MoveNumberOne:
10586 if (lastLoadGameStart == GNUChessGame) {
10587 /* GNUChessGames have numbers, but they aren't move numbers */
10588 if (appData.debugMode)
10589 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10590 yy_text, (int) moveType);
10591 return LoadGameOneMove(EndOfFile); /* tail recursion */
10593 /* else fall thru */
10598 /* Reached start of next game in file */
10599 if (appData.debugMode)
10600 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10601 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10607 if (WhiteOnMove(currentMove)) {
10608 GameEnds(BlackWins, "Black mates", GE_FILE);
10610 GameEnds(WhiteWins, "White mates", GE_FILE);
10614 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10620 case PositionDiagram: /* should not happen; ignore */
10621 case ElapsedTime: /* ignore */
10622 case NAG: /* ignore */
10623 if (appData.debugMode)
10624 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10625 yy_text, (int) moveType);
10626 return LoadGameOneMove(EndOfFile); /* tail recursion */
10629 if (appData.testLegality) {
10630 if (appData.debugMode)
10631 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10632 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10633 (forwardMostMove / 2) + 1,
10634 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10635 DisplayError(move, 0);
10638 if (appData.debugMode)
10639 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10640 yy_text, currentMoveString);
10641 fromX = currentMoveString[0] - AAA;
10642 fromY = currentMoveString[1] - ONE;
10643 toX = currentMoveString[2] - AAA;
10644 toY = currentMoveString[3] - ONE;
10645 promoChar = currentMoveString[4];
10649 case AmbiguousMove:
10650 if (appData.debugMode)
10651 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10652 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10653 (forwardMostMove / 2) + 1,
10654 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10655 DisplayError(move, 0);
10660 case ImpossibleMove:
10661 if (appData.debugMode)
10662 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10663 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10664 (forwardMostMove / 2) + 1,
10665 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10666 DisplayError(move, 0);
10672 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10673 DrawPosition(FALSE, boards[currentMove]);
10674 DisplayBothClocks();
10675 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10676 DisplayComment(currentMove - 1, commentList[currentMove]);
10678 (void) StopLoadGameTimer();
10680 cmailOldMove = forwardMostMove;
10683 /* currentMoveString is set as a side-effect of yylex */
10685 thinkOutput[0] = NULLCHAR;
10686 MakeMove(fromX, fromY, toX, toY, promoChar);
10687 currentMove = forwardMostMove;
10692 /* Load the nth game from the given file */
10694 LoadGameFromFile(filename, n, title, useList)
10698 /*Boolean*/ int useList;
10703 if (strcmp(filename, "-") == 0) {
10707 f = fopen(filename, "rb");
10709 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10710 DisplayError(buf, errno);
10714 if (fseek(f, 0, 0) == -1) {
10715 /* f is not seekable; probably a pipe */
10718 if (useList && n == 0) {
10719 int error = GameListBuild(f);
10721 DisplayError(_("Cannot build game list"), error);
10722 } else if (!ListEmpty(&gameList) &&
10723 ((ListGame *) gameList.tailPred)->number > 1) {
10724 GameListPopUp(f, title);
10731 return LoadGame(f, n, title, FALSE);
10736 MakeRegisteredMove()
10738 int fromX, fromY, toX, toY;
10740 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10741 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10744 if (appData.debugMode)
10745 fprintf(debugFP, "Restoring %s for game %d\n",
10746 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10748 thinkOutput[0] = NULLCHAR;
10749 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10750 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10751 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10752 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10753 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10754 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10755 MakeMove(fromX, fromY, toX, toY, promoChar);
10756 ShowMove(fromX, fromY, toX, toY);
10758 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10765 if (WhiteOnMove(currentMove)) {
10766 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10768 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10773 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10780 if (WhiteOnMove(currentMove)) {
10781 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10783 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10788 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10799 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10801 CmailLoadGame(f, gameNumber, title, useList)
10809 if (gameNumber > nCmailGames) {
10810 DisplayError(_("No more games in this message"), 0);
10813 if (f == lastLoadGameFP) {
10814 int offset = gameNumber - lastLoadGameNumber;
10816 cmailMsg[0] = NULLCHAR;
10817 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10818 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10819 nCmailMovesRegistered--;
10821 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10822 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10823 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10826 if (! RegisterMove()) return FALSE;
10830 retVal = LoadGame(f, gameNumber, title, useList);
10832 /* Make move registered during previous look at this game, if any */
10833 MakeRegisteredMove();
10835 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10836 commentList[currentMove]
10837 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10838 DisplayComment(currentMove - 1, commentList[currentMove]);
10844 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10849 int gameNumber = lastLoadGameNumber + offset;
10850 if (lastLoadGameFP == NULL) {
10851 DisplayError(_("No game has been loaded yet"), 0);
10854 if (gameNumber <= 0) {
10855 DisplayError(_("Can't back up any further"), 0);
10858 if (cmailMsgLoaded) {
10859 return CmailLoadGame(lastLoadGameFP, gameNumber,
10860 lastLoadGameTitle, lastLoadGameUseList);
10862 return LoadGame(lastLoadGameFP, gameNumber,
10863 lastLoadGameTitle, lastLoadGameUseList);
10869 /* Load the nth game from open file f */
10871 LoadGame(f, gameNumber, title, useList)
10879 int gn = gameNumber;
10880 ListGame *lg = NULL;
10881 int numPGNTags = 0;
10883 GameMode oldGameMode;
10884 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10886 if (appData.debugMode)
10887 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10889 if (gameMode == Training )
10890 SetTrainingModeOff();
10892 oldGameMode = gameMode;
10893 if (gameMode != BeginningOfGame) {
10894 Reset(FALSE, TRUE);
10898 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10899 fclose(lastLoadGameFP);
10903 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10906 fseek(f, lg->offset, 0);
10907 GameListHighlight(gameNumber);
10911 DisplayError(_("Game number out of range"), 0);
10916 if (fseek(f, 0, 0) == -1) {
10917 if (f == lastLoadGameFP ?
10918 gameNumber == lastLoadGameNumber + 1 :
10922 DisplayError(_("Can't seek on game file"), 0);
10927 lastLoadGameFP = f;
10928 lastLoadGameNumber = gameNumber;
10929 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10930 lastLoadGameUseList = useList;
10934 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10935 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10936 lg->gameInfo.black);
10938 } else if (*title != NULLCHAR) {
10939 if (gameNumber > 1) {
10940 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10943 DisplayTitle(title);
10947 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10948 gameMode = PlayFromGameFile;
10952 currentMove = forwardMostMove = backwardMostMove = 0;
10953 CopyBoard(boards[0], initialPosition);
10957 * Skip the first gn-1 games in the file.
10958 * Also skip over anything that precedes an identifiable
10959 * start of game marker, to avoid being confused by
10960 * garbage at the start of the file. Currently
10961 * recognized start of game markers are the move number "1",
10962 * the pattern "gnuchess .* game", the pattern
10963 * "^[#;%] [^ ]* game file", and a PGN tag block.
10964 * A game that starts with one of the latter two patterns
10965 * will also have a move number 1, possibly
10966 * following a position diagram.
10967 * 5-4-02: Let's try being more lenient and allowing a game to
10968 * start with an unnumbered move. Does that break anything?
10970 cm = lastLoadGameStart = EndOfFile;
10972 yyboardindex = forwardMostMove;
10973 cm = (ChessMove) Myylex();
10976 if (cmailMsgLoaded) {
10977 nCmailGames = CMAIL_MAX_GAMES - gn;
10980 DisplayError(_("Game not found in file"), 0);
10987 lastLoadGameStart = cm;
10990 case MoveNumberOne:
10991 switch (lastLoadGameStart) {
10996 case MoveNumberOne:
10998 gn--; /* count this game */
10999 lastLoadGameStart = cm;
11008 switch (lastLoadGameStart) {
11011 case MoveNumberOne:
11013 gn--; /* count this game */
11014 lastLoadGameStart = cm;
11017 lastLoadGameStart = cm; /* game counted already */
11025 yyboardindex = forwardMostMove;
11026 cm = (ChessMove) Myylex();
11027 } while (cm == PGNTag || cm == Comment);
11034 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11035 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11036 != CMAIL_OLD_RESULT) {
11038 cmailResult[ CMAIL_MAX_GAMES
11039 - gn - 1] = CMAIL_OLD_RESULT;
11045 /* Only a NormalMove can be at the start of a game
11046 * without a position diagram. */
11047 if (lastLoadGameStart == EndOfFile ) {
11049 lastLoadGameStart = MoveNumberOne;
11058 if (appData.debugMode)
11059 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11061 if (cm == XBoardGame) {
11062 /* Skip any header junk before position diagram and/or move 1 */
11064 yyboardindex = forwardMostMove;
11065 cm = (ChessMove) Myylex();
11067 if (cm == EndOfFile ||
11068 cm == GNUChessGame || cm == XBoardGame) {
11069 /* Empty game; pretend end-of-file and handle later */
11074 if (cm == MoveNumberOne || cm == PositionDiagram ||
11075 cm == PGNTag || cm == Comment)
11078 } else if (cm == GNUChessGame) {
11079 if (gameInfo.event != NULL) {
11080 free(gameInfo.event);
11082 gameInfo.event = StrSave(yy_text);
11085 startedFromSetupPosition = FALSE;
11086 while (cm == PGNTag) {
11087 if (appData.debugMode)
11088 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11089 err = ParsePGNTag(yy_text, &gameInfo);
11090 if (!err) numPGNTags++;
11092 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11093 if(gameInfo.variant != oldVariant) {
11094 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11095 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11096 InitPosition(TRUE);
11097 oldVariant = gameInfo.variant;
11098 if (appData.debugMode)
11099 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11103 if (gameInfo.fen != NULL) {
11104 Board initial_position;
11105 startedFromSetupPosition = TRUE;
11106 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11108 DisplayError(_("Bad FEN position in file"), 0);
11111 CopyBoard(boards[0], initial_position);
11112 if (blackPlaysFirst) {
11113 currentMove = forwardMostMove = backwardMostMove = 1;
11114 CopyBoard(boards[1], initial_position);
11115 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11116 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11117 timeRemaining[0][1] = whiteTimeRemaining;
11118 timeRemaining[1][1] = blackTimeRemaining;
11119 if (commentList[0] != NULL) {
11120 commentList[1] = commentList[0];
11121 commentList[0] = NULL;
11124 currentMove = forwardMostMove = backwardMostMove = 0;
11126 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11128 initialRulePlies = FENrulePlies;
11129 for( i=0; i< nrCastlingRights; i++ )
11130 initialRights[i] = initial_position[CASTLING][i];
11132 yyboardindex = forwardMostMove;
11133 free(gameInfo.fen);
11134 gameInfo.fen = NULL;
11137 yyboardindex = forwardMostMove;
11138 cm = (ChessMove) Myylex();
11140 /* Handle comments interspersed among the tags */
11141 while (cm == Comment) {
11143 if (appData.debugMode)
11144 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11146 AppendComment(currentMove, p, FALSE);
11147 yyboardindex = forwardMostMove;
11148 cm = (ChessMove) Myylex();
11152 /* don't rely on existence of Event tag since if game was
11153 * pasted from clipboard the Event tag may not exist
11155 if (numPGNTags > 0){
11157 if (gameInfo.variant == VariantNormal) {
11158 VariantClass v = StringToVariant(gameInfo.event);
11159 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11160 if(v < VariantShogi) gameInfo.variant = v;
11163 if( appData.autoDisplayTags ) {
11164 tags = PGNTags(&gameInfo);
11165 TagsPopUp(tags, CmailMsg());
11170 /* Make something up, but don't display it now */
11175 if (cm == PositionDiagram) {
11178 Board initial_position;
11180 if (appData.debugMode)
11181 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11183 if (!startedFromSetupPosition) {
11185 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11186 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11197 initial_position[i][j++] = CharToPiece(*p);
11200 while (*p == ' ' || *p == '\t' ||
11201 *p == '\n' || *p == '\r') p++;
11203 if (strncmp(p, "black", strlen("black"))==0)
11204 blackPlaysFirst = TRUE;
11206 blackPlaysFirst = FALSE;
11207 startedFromSetupPosition = TRUE;
11209 CopyBoard(boards[0], initial_position);
11210 if (blackPlaysFirst) {
11211 currentMove = forwardMostMove = backwardMostMove = 1;
11212 CopyBoard(boards[1], initial_position);
11213 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11214 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11215 timeRemaining[0][1] = whiteTimeRemaining;
11216 timeRemaining[1][1] = blackTimeRemaining;
11217 if (commentList[0] != NULL) {
11218 commentList[1] = commentList[0];
11219 commentList[0] = NULL;
11222 currentMove = forwardMostMove = backwardMostMove = 0;
11225 yyboardindex = forwardMostMove;
11226 cm = (ChessMove) Myylex();
11229 if (first.pr == NoProc) {
11230 StartChessProgram(&first);
11232 InitChessProgram(&first, FALSE);
11233 SendToProgram("force\n", &first);
11234 if (startedFromSetupPosition) {
11235 SendBoard(&first, forwardMostMove);
11236 if (appData.debugMode) {
11237 fprintf(debugFP, "Load Game\n");
11239 DisplayBothClocks();
11242 /* [HGM] server: flag to write setup moves in broadcast file as one */
11243 loadFlag = appData.suppressLoadMoves;
11245 while (cm == Comment) {
11247 if (appData.debugMode)
11248 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11250 AppendComment(currentMove, p, FALSE);
11251 yyboardindex = forwardMostMove;
11252 cm = (ChessMove) Myylex();
11255 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11256 cm == WhiteWins || cm == BlackWins ||
11257 cm == GameIsDrawn || cm == GameUnfinished) {
11258 DisplayMessage("", _("No moves in game"));
11259 if (cmailMsgLoaded) {
11260 if (appData.debugMode)
11261 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11265 DrawPosition(FALSE, boards[currentMove]);
11266 DisplayBothClocks();
11267 gameMode = EditGame;
11274 // [HGM] PV info: routine tests if comment empty
11275 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11276 DisplayComment(currentMove - 1, commentList[currentMove]);
11278 if (!matchMode && appData.timeDelay != 0)
11279 DrawPosition(FALSE, boards[currentMove]);
11281 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11282 programStats.ok_to_send = 1;
11285 /* if the first token after the PGN tags is a move
11286 * and not move number 1, retrieve it from the parser
11288 if (cm != MoveNumberOne)
11289 LoadGameOneMove(cm);
11291 /* load the remaining moves from the file */
11292 while (LoadGameOneMove(EndOfFile)) {
11293 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11294 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11297 /* rewind to the start of the game */
11298 currentMove = backwardMostMove;
11300 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11302 if (oldGameMode == AnalyzeFile ||
11303 oldGameMode == AnalyzeMode) {
11304 AnalyzeFileEvent();
11307 if (matchMode || appData.timeDelay == 0) {
11309 gameMode = EditGame;
11311 } else if (appData.timeDelay > 0) {
11312 AutoPlayGameLoop();
11315 if (appData.debugMode)
11316 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11318 loadFlag = 0; /* [HGM] true game starts */
11322 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11324 ReloadPosition(offset)
11327 int positionNumber = lastLoadPositionNumber + offset;
11328 if (lastLoadPositionFP == NULL) {
11329 DisplayError(_("No position has been loaded yet"), 0);
11332 if (positionNumber <= 0) {
11333 DisplayError(_("Can't back up any further"), 0);
11336 return LoadPosition(lastLoadPositionFP, positionNumber,
11337 lastLoadPositionTitle);
11340 /* Load the nth position from the given file */
11342 LoadPositionFromFile(filename, n, title)
11350 if (strcmp(filename, "-") == 0) {
11351 return LoadPosition(stdin, n, "stdin");
11353 f = fopen(filename, "rb");
11355 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11356 DisplayError(buf, errno);
11359 return LoadPosition(f, n, title);
11364 /* Load the nth position from the given open file, and close it */
11366 LoadPosition(f, positionNumber, title)
11368 int positionNumber;
11371 char *p, line[MSG_SIZ];
11372 Board initial_position;
11373 int i, j, fenMode, pn;
11375 if (gameMode == Training )
11376 SetTrainingModeOff();
11378 if (gameMode != BeginningOfGame) {
11379 Reset(FALSE, TRUE);
11381 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11382 fclose(lastLoadPositionFP);
11384 if (positionNumber == 0) positionNumber = 1;
11385 lastLoadPositionFP = f;
11386 lastLoadPositionNumber = positionNumber;
11387 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11388 if (first.pr == NoProc) {
11389 StartChessProgram(&first);
11390 InitChessProgram(&first, FALSE);
11392 pn = positionNumber;
11393 if (positionNumber < 0) {
11394 /* Negative position number means to seek to that byte offset */
11395 if (fseek(f, -positionNumber, 0) == -1) {
11396 DisplayError(_("Can't seek on position file"), 0);
11401 if (fseek(f, 0, 0) == -1) {
11402 if (f == lastLoadPositionFP ?
11403 positionNumber == lastLoadPositionNumber + 1 :
11404 positionNumber == 1) {
11407 DisplayError(_("Can't seek on position file"), 0);
11412 /* See if this file is FEN or old-style xboard */
11413 if (fgets(line, MSG_SIZ, f) == NULL) {
11414 DisplayError(_("Position not found in file"), 0);
11417 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11418 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11421 if (fenMode || line[0] == '#') pn--;
11423 /* skip positions before number pn */
11424 if (fgets(line, MSG_SIZ, f) == NULL) {
11426 DisplayError(_("Position not found in file"), 0);
11429 if (fenMode || line[0] == '#') pn--;
11434 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11435 DisplayError(_("Bad FEN position in file"), 0);
11439 (void) fgets(line, MSG_SIZ, f);
11440 (void) fgets(line, MSG_SIZ, f);
11442 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11443 (void) fgets(line, MSG_SIZ, f);
11444 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11447 initial_position[i][j++] = CharToPiece(*p);
11451 blackPlaysFirst = FALSE;
11453 (void) fgets(line, MSG_SIZ, f);
11454 if (strncmp(line, "black", strlen("black"))==0)
11455 blackPlaysFirst = TRUE;
11458 startedFromSetupPosition = TRUE;
11460 SendToProgram("force\n", &first);
11461 CopyBoard(boards[0], initial_position);
11462 if (blackPlaysFirst) {
11463 currentMove = forwardMostMove = backwardMostMove = 1;
11464 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11465 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11466 CopyBoard(boards[1], initial_position);
11467 DisplayMessage("", _("Black to play"));
11469 currentMove = forwardMostMove = backwardMostMove = 0;
11470 DisplayMessage("", _("White to play"));
11472 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11473 SendBoard(&first, forwardMostMove);
11474 if (appData.debugMode) {
11476 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11477 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11478 fprintf(debugFP, "Load Position\n");
11481 if (positionNumber > 1) {
11482 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11483 DisplayTitle(line);
11485 DisplayTitle(title);
11487 gameMode = EditGame;
11490 timeRemaining[0][1] = whiteTimeRemaining;
11491 timeRemaining[1][1] = blackTimeRemaining;
11492 DrawPosition(FALSE, boards[currentMove]);
11499 CopyPlayerNameIntoFileName(dest, src)
11502 while (*src != NULLCHAR && *src != ',') {
11507 *(*dest)++ = *src++;
11512 char *DefaultFileName(ext)
11515 static char def[MSG_SIZ];
11518 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11520 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11522 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11524 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11531 /* Save the current game to the given file */
11533 SaveGameToFile(filename, append)
11541 if (strcmp(filename, "-") == 0) {
11542 return SaveGame(stdout, 0, NULL);
11544 f = fopen(filename, append ? "a" : "w");
11546 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11547 DisplayError(buf, errno);
11550 safeStrCpy(buf, lastMsg, MSG_SIZ);
11551 DisplayMessage(_("Waiting for access to save file"), "");
11552 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11553 DisplayMessage(_("Saving game"), "");
11554 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11555 result = SaveGame(f, 0, NULL);
11556 DisplayMessage(buf, "");
11566 static char buf[MSG_SIZ];
11569 p = strchr(str, ' ');
11570 if (p == NULL) return str;
11571 strncpy(buf, str, p - str);
11572 buf[p - str] = NULLCHAR;
11576 #define PGN_MAX_LINE 75
11578 #define PGN_SIDE_WHITE 0
11579 #define PGN_SIDE_BLACK 1
11582 static int FindFirstMoveOutOfBook( int side )
11586 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11587 int index = backwardMostMove;
11588 int has_book_hit = 0;
11590 if( (index % 2) != side ) {
11594 while( index < forwardMostMove ) {
11595 /* Check to see if engine is in book */
11596 int depth = pvInfoList[index].depth;
11597 int score = pvInfoList[index].score;
11603 else if( score == 0 && depth == 63 ) {
11604 in_book = 1; /* Zappa */
11606 else if( score == 2 && depth == 99 ) {
11607 in_book = 1; /* Abrok */
11610 has_book_hit += in_book;
11626 void GetOutOfBookInfo( char * buf )
11630 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11632 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11633 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11637 if( oob[0] >= 0 || oob[1] >= 0 ) {
11638 for( i=0; i<2; i++ ) {
11642 if( i > 0 && oob[0] >= 0 ) {
11643 strcat( buf, " " );
11646 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11647 sprintf( buf+strlen(buf), "%s%.2f",
11648 pvInfoList[idx].score >= 0 ? "+" : "",
11649 pvInfoList[idx].score / 100.0 );
11655 /* Save game in PGN style and close the file */
11660 int i, offset, linelen, newblock;
11664 int movelen, numlen, blank;
11665 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11667 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11669 tm = time((time_t *) NULL);
11671 PrintPGNTags(f, &gameInfo);
11673 if (backwardMostMove > 0 || startedFromSetupPosition) {
11674 char *fen = PositionToFEN(backwardMostMove, NULL);
11675 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11676 fprintf(f, "\n{--------------\n");
11677 PrintPosition(f, backwardMostMove);
11678 fprintf(f, "--------------}\n");
11682 /* [AS] Out of book annotation */
11683 if( appData.saveOutOfBookInfo ) {
11686 GetOutOfBookInfo( buf );
11688 if( buf[0] != '\0' ) {
11689 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11696 i = backwardMostMove;
11700 while (i < forwardMostMove) {
11701 /* Print comments preceding this move */
11702 if (commentList[i] != NULL) {
11703 if (linelen > 0) fprintf(f, "\n");
11704 fprintf(f, "%s", commentList[i]);
11709 /* Format move number */
11711 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11714 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11716 numtext[0] = NULLCHAR;
11718 numlen = strlen(numtext);
11721 /* Print move number */
11722 blank = linelen > 0 && numlen > 0;
11723 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11732 fprintf(f, "%s", numtext);
11736 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11737 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11740 blank = linelen > 0 && movelen > 0;
11741 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11750 fprintf(f, "%s", move_buffer);
11751 linelen += movelen;
11753 /* [AS] Add PV info if present */
11754 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11755 /* [HGM] add time */
11756 char buf[MSG_SIZ]; int seconds;
11758 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11764 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11767 seconds = (seconds + 4)/10; // round to full seconds
11769 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11771 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11774 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11775 pvInfoList[i].score >= 0 ? "+" : "",
11776 pvInfoList[i].score / 100.0,
11777 pvInfoList[i].depth,
11780 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11782 /* Print score/depth */
11783 blank = linelen > 0 && movelen > 0;
11784 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11793 fprintf(f, "%s", move_buffer);
11794 linelen += movelen;
11800 /* Start a new line */
11801 if (linelen > 0) fprintf(f, "\n");
11803 /* Print comments after last move */
11804 if (commentList[i] != NULL) {
11805 fprintf(f, "%s\n", commentList[i]);
11809 if (gameInfo.resultDetails != NULL &&
11810 gameInfo.resultDetails[0] != NULLCHAR) {
11811 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11812 PGNResult(gameInfo.result));
11814 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11818 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11822 /* Save game in old style and close the file */
11824 SaveGameOldStyle(f)
11830 tm = time((time_t *) NULL);
11832 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11835 if (backwardMostMove > 0 || startedFromSetupPosition) {
11836 fprintf(f, "\n[--------------\n");
11837 PrintPosition(f, backwardMostMove);
11838 fprintf(f, "--------------]\n");
11843 i = backwardMostMove;
11844 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11846 while (i < forwardMostMove) {
11847 if (commentList[i] != NULL) {
11848 fprintf(f, "[%s]\n", commentList[i]);
11851 if ((i % 2) == 1) {
11852 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11855 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11857 if (commentList[i] != NULL) {
11861 if (i >= forwardMostMove) {
11865 fprintf(f, "%s\n", parseList[i]);
11870 if (commentList[i] != NULL) {
11871 fprintf(f, "[%s]\n", commentList[i]);
11874 /* This isn't really the old style, but it's close enough */
11875 if (gameInfo.resultDetails != NULL &&
11876 gameInfo.resultDetails[0] != NULLCHAR) {
11877 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11878 gameInfo.resultDetails);
11880 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11887 /* Save the current game to open file f and close the file */
11889 SaveGame(f, dummy, dummy2)
11894 if (gameMode == EditPosition) EditPositionDone(TRUE);
11895 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11896 if (appData.oldSaveStyle)
11897 return SaveGameOldStyle(f);
11899 return SaveGamePGN(f);
11902 /* Save the current position to the given file */
11904 SavePositionToFile(filename)
11910 if (strcmp(filename, "-") == 0) {
11911 return SavePosition(stdout, 0, NULL);
11913 f = fopen(filename, "a");
11915 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11916 DisplayError(buf, errno);
11919 safeStrCpy(buf, lastMsg, MSG_SIZ);
11920 DisplayMessage(_("Waiting for access to save file"), "");
11921 flock(fileno(f), LOCK_EX); // [HGM] lock
11922 DisplayMessage(_("Saving position"), "");
11923 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11924 SavePosition(f, 0, NULL);
11925 DisplayMessage(buf, "");
11931 /* Save the current position to the given open file and close the file */
11933 SavePosition(f, dummy, dummy2)
11941 if (gameMode == EditPosition) EditPositionDone(TRUE);
11942 if (appData.oldSaveStyle) {
11943 tm = time((time_t *) NULL);
11945 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11947 fprintf(f, "[--------------\n");
11948 PrintPosition(f, currentMove);
11949 fprintf(f, "--------------]\n");
11951 fen = PositionToFEN(currentMove, NULL);
11952 fprintf(f, "%s\n", fen);
11960 ReloadCmailMsgEvent(unregister)
11964 static char *inFilename = NULL;
11965 static char *outFilename;
11967 struct stat inbuf, outbuf;
11970 /* Any registered moves are unregistered if unregister is set, */
11971 /* i.e. invoked by the signal handler */
11973 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11974 cmailMoveRegistered[i] = FALSE;
11975 if (cmailCommentList[i] != NULL) {
11976 free(cmailCommentList[i]);
11977 cmailCommentList[i] = NULL;
11980 nCmailMovesRegistered = 0;
11983 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11984 cmailResult[i] = CMAIL_NOT_RESULT;
11988 if (inFilename == NULL) {
11989 /* Because the filenames are static they only get malloced once */
11990 /* and they never get freed */
11991 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11992 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11994 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11995 sprintf(outFilename, "%s.out", appData.cmailGameName);
11998 status = stat(outFilename, &outbuf);
12000 cmailMailedMove = FALSE;
12002 status = stat(inFilename, &inbuf);
12003 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12006 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12007 counts the games, notes how each one terminated, etc.
12009 It would be nice to remove this kludge and instead gather all
12010 the information while building the game list. (And to keep it
12011 in the game list nodes instead of having a bunch of fixed-size
12012 parallel arrays.) Note this will require getting each game's
12013 termination from the PGN tags, as the game list builder does
12014 not process the game moves. --mann
12016 cmailMsgLoaded = TRUE;
12017 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12019 /* Load first game in the file or popup game menu */
12020 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12022 #endif /* !WIN32 */
12030 char string[MSG_SIZ];
12032 if ( cmailMailedMove
12033 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12034 return TRUE; /* Allow free viewing */
12037 /* Unregister move to ensure that we don't leave RegisterMove */
12038 /* with the move registered when the conditions for registering no */
12040 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12041 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12042 nCmailMovesRegistered --;
12044 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12046 free(cmailCommentList[lastLoadGameNumber - 1]);
12047 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12051 if (cmailOldMove == -1) {
12052 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12056 if (currentMove > cmailOldMove + 1) {
12057 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12061 if (currentMove < cmailOldMove) {
12062 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12066 if (forwardMostMove > currentMove) {
12067 /* Silently truncate extra moves */
12071 if ( (currentMove == cmailOldMove + 1)
12072 || ( (currentMove == cmailOldMove)
12073 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12074 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12075 if (gameInfo.result != GameUnfinished) {
12076 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12079 if (commentList[currentMove] != NULL) {
12080 cmailCommentList[lastLoadGameNumber - 1]
12081 = StrSave(commentList[currentMove]);
12083 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12085 if (appData.debugMode)
12086 fprintf(debugFP, "Saving %s for game %d\n",
12087 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12089 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12091 f = fopen(string, "w");
12092 if (appData.oldSaveStyle) {
12093 SaveGameOldStyle(f); /* also closes the file */
12095 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12096 f = fopen(string, "w");
12097 SavePosition(f, 0, NULL); /* also closes the file */
12099 fprintf(f, "{--------------\n");
12100 PrintPosition(f, currentMove);
12101 fprintf(f, "--------------}\n\n");
12103 SaveGame(f, 0, NULL); /* also closes the file*/
12106 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12107 nCmailMovesRegistered ++;
12108 } else if (nCmailGames == 1) {
12109 DisplayError(_("You have not made a move yet"), 0);
12120 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12121 FILE *commandOutput;
12122 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12123 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12129 if (! cmailMsgLoaded) {
12130 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12134 if (nCmailGames == nCmailResults) {
12135 DisplayError(_("No unfinished games"), 0);
12139 #if CMAIL_PROHIBIT_REMAIL
12140 if (cmailMailedMove) {
12141 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);
12142 DisplayError(msg, 0);
12147 if (! (cmailMailedMove || RegisterMove())) return;
12149 if ( cmailMailedMove
12150 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12151 snprintf(string, MSG_SIZ, partCommandString,
12152 appData.debugMode ? " -v" : "", appData.cmailGameName);
12153 commandOutput = popen(string, "r");
12155 if (commandOutput == NULL) {
12156 DisplayError(_("Failed to invoke cmail"), 0);
12158 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12159 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12161 if (nBuffers > 1) {
12162 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12163 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12164 nBytes = MSG_SIZ - 1;
12166 (void) memcpy(msg, buffer, nBytes);
12168 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12170 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12171 cmailMailedMove = TRUE; /* Prevent >1 moves */
12174 for (i = 0; i < nCmailGames; i ++) {
12175 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12180 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12182 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12184 appData.cmailGameName,
12186 LoadGameFromFile(buffer, 1, buffer, FALSE);
12187 cmailMsgLoaded = FALSE;
12191 DisplayInformation(msg);
12192 pclose(commandOutput);
12195 if ((*cmailMsg) != '\0') {
12196 DisplayInformation(cmailMsg);
12201 #endif /* !WIN32 */
12210 int prependComma = 0;
12212 char string[MSG_SIZ]; /* Space for game-list */
12215 if (!cmailMsgLoaded) return "";
12217 if (cmailMailedMove) {
12218 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12220 /* Create a list of games left */
12221 snprintf(string, MSG_SIZ, "[");
12222 for (i = 0; i < nCmailGames; i ++) {
12223 if (! ( cmailMoveRegistered[i]
12224 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12225 if (prependComma) {
12226 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12228 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12232 strcat(string, number);
12235 strcat(string, "]");
12237 if (nCmailMovesRegistered + nCmailResults == 0) {
12238 switch (nCmailGames) {
12240 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12244 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12248 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12253 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12255 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12260 if (nCmailResults == nCmailGames) {
12261 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12263 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12268 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12280 if (gameMode == Training)
12281 SetTrainingModeOff();
12284 cmailMsgLoaded = FALSE;
12285 if (appData.icsActive) {
12286 SendToICS(ics_prefix);
12287 SendToICS("refresh\n");
12297 /* Give up on clean exit */
12301 /* Keep trying for clean exit */
12305 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12307 if (telnetISR != NULL) {
12308 RemoveInputSource(telnetISR);
12310 if (icsPR != NoProc) {
12311 DestroyChildProcess(icsPR, TRUE);
12314 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12315 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12317 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12318 /* make sure this other one finishes before killing it! */
12319 if(endingGame) { int count = 0;
12320 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12321 while(endingGame && count++ < 10) DoSleep(1);
12322 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12325 /* Kill off chess programs */
12326 if (first.pr != NoProc) {
12329 DoSleep( appData.delayBeforeQuit );
12330 SendToProgram("quit\n", &first);
12331 DoSleep( appData.delayAfterQuit );
12332 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12334 if (second.pr != NoProc) {
12335 DoSleep( appData.delayBeforeQuit );
12336 SendToProgram("quit\n", &second);
12337 DoSleep( appData.delayAfterQuit );
12338 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12340 if (first.isr != NULL) {
12341 RemoveInputSource(first.isr);
12343 if (second.isr != NULL) {
12344 RemoveInputSource(second.isr);
12347 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12348 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12350 ShutDownFrontEnd();
12357 if (appData.debugMode)
12358 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12362 if (gameMode == MachinePlaysWhite ||
12363 gameMode == MachinePlaysBlack) {
12366 DisplayBothClocks();
12368 if (gameMode == PlayFromGameFile) {
12369 if (appData.timeDelay >= 0)
12370 AutoPlayGameLoop();
12371 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12372 Reset(FALSE, TRUE);
12373 SendToICS(ics_prefix);
12374 SendToICS("refresh\n");
12375 } else if (currentMove < forwardMostMove) {
12376 ForwardInner(forwardMostMove);
12378 pauseExamInvalid = FALSE;
12380 switch (gameMode) {
12384 pauseExamForwardMostMove = forwardMostMove;
12385 pauseExamInvalid = FALSE;
12388 case IcsPlayingWhite:
12389 case IcsPlayingBlack:
12393 case PlayFromGameFile:
12394 (void) StopLoadGameTimer();
12398 case BeginningOfGame:
12399 if (appData.icsActive) return;
12400 /* else fall through */
12401 case MachinePlaysWhite:
12402 case MachinePlaysBlack:
12403 case TwoMachinesPlay:
12404 if (forwardMostMove == 0)
12405 return; /* don't pause if no one has moved */
12406 if ((gameMode == MachinePlaysWhite &&
12407 !WhiteOnMove(forwardMostMove)) ||
12408 (gameMode == MachinePlaysBlack &&
12409 WhiteOnMove(forwardMostMove))) {
12422 char title[MSG_SIZ];
12424 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12425 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12427 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12428 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12429 parseList[currentMove - 1]);
12432 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12439 char *tags = PGNTags(&gameInfo);
12441 EditTagsPopUp(tags, NULL);
12448 if (appData.noChessProgram || gameMode == AnalyzeMode)
12451 if (gameMode != AnalyzeFile) {
12452 if (!appData.icsEngineAnalyze) {
12454 if (gameMode != EditGame) return;
12456 ResurrectChessProgram();
12457 SendToProgram("analyze\n", &first);
12458 first.analyzing = TRUE;
12459 /*first.maybeThinking = TRUE;*/
12460 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12461 EngineOutputPopUp();
12463 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12468 StartAnalysisClock();
12469 GetTimeMark(&lastNodeCountTime);
12476 if (appData.noChessProgram || gameMode == AnalyzeFile)
12479 if (gameMode != AnalyzeMode) {
12481 if (gameMode != EditGame) return;
12482 ResurrectChessProgram();
12483 SendToProgram("analyze\n", &first);
12484 first.analyzing = TRUE;
12485 /*first.maybeThinking = TRUE;*/
12486 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12487 EngineOutputPopUp();
12489 gameMode = AnalyzeFile;
12494 StartAnalysisClock();
12495 GetTimeMark(&lastNodeCountTime);
12500 MachineWhiteEvent()
12503 char *bookHit = NULL;
12505 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12509 if (gameMode == PlayFromGameFile ||
12510 gameMode == TwoMachinesPlay ||
12511 gameMode == Training ||
12512 gameMode == AnalyzeMode ||
12513 gameMode == EndOfGame)
12516 if (gameMode == EditPosition)
12517 EditPositionDone(TRUE);
12519 if (!WhiteOnMove(currentMove)) {
12520 DisplayError(_("It is not White's turn"), 0);
12524 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12527 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12528 gameMode == AnalyzeFile)
12531 ResurrectChessProgram(); /* in case it isn't running */
12532 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12533 gameMode = MachinePlaysWhite;
12536 gameMode = MachinePlaysWhite;
12540 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12542 if (first.sendName) {
12543 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12544 SendToProgram(buf, &first);
12546 if (first.sendTime) {
12547 if (first.useColors) {
12548 SendToProgram("black\n", &first); /*gnu kludge*/
12550 SendTimeRemaining(&first, TRUE);
12552 if (first.useColors) {
12553 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12555 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12556 SetMachineThinkingEnables();
12557 first.maybeThinking = TRUE;
12561 if (appData.autoFlipView && !flipView) {
12562 flipView = !flipView;
12563 DrawPosition(FALSE, NULL);
12564 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12567 if(bookHit) { // [HGM] book: simulate book reply
12568 static char bookMove[MSG_SIZ]; // a bit generous?
12570 programStats.nodes = programStats.depth = programStats.time =
12571 programStats.score = programStats.got_only_move = 0;
12572 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12574 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12575 strcat(bookMove, bookHit);
12576 HandleMachineMove(bookMove, &first);
12581 MachineBlackEvent()
12584 char *bookHit = NULL;
12586 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12590 if (gameMode == PlayFromGameFile ||
12591 gameMode == TwoMachinesPlay ||
12592 gameMode == Training ||
12593 gameMode == AnalyzeMode ||
12594 gameMode == EndOfGame)
12597 if (gameMode == EditPosition)
12598 EditPositionDone(TRUE);
12600 if (WhiteOnMove(currentMove)) {
12601 DisplayError(_("It is not Black's turn"), 0);
12605 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12608 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12609 gameMode == AnalyzeFile)
12612 ResurrectChessProgram(); /* in case it isn't running */
12613 gameMode = MachinePlaysBlack;
12617 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12619 if (first.sendName) {
12620 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12621 SendToProgram(buf, &first);
12623 if (first.sendTime) {
12624 if (first.useColors) {
12625 SendToProgram("white\n", &first); /*gnu kludge*/
12627 SendTimeRemaining(&first, FALSE);
12629 if (first.useColors) {
12630 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12632 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12633 SetMachineThinkingEnables();
12634 first.maybeThinking = TRUE;
12637 if (appData.autoFlipView && flipView) {
12638 flipView = !flipView;
12639 DrawPosition(FALSE, NULL);
12640 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12642 if(bookHit) { // [HGM] book: simulate book reply
12643 static char bookMove[MSG_SIZ]; // a bit generous?
12645 programStats.nodes = programStats.depth = programStats.time =
12646 programStats.score = programStats.got_only_move = 0;
12647 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12649 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12650 strcat(bookMove, bookHit);
12651 HandleMachineMove(bookMove, &first);
12657 DisplayTwoMachinesTitle()
12660 if (appData.matchGames > 0) {
12661 if (first.twoMachinesColor[0] == 'w') {
12662 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12663 gameInfo.white, gameInfo.black,
12664 first.matchWins, second.matchWins,
12665 matchGame - 1 - (first.matchWins + second.matchWins));
12667 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12668 gameInfo.white, gameInfo.black,
12669 second.matchWins, first.matchWins,
12670 matchGame - 1 - (first.matchWins + second.matchWins));
12673 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12679 SettingsMenuIfReady()
12681 if (second.lastPing != second.lastPong) {
12682 DisplayMessage("", _("Waiting for second chess program"));
12683 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12687 DisplayMessage("", "");
12688 SettingsPopUp(&second);
12692 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12695 if (cps->pr == NULL) {
12696 StartChessProgram(cps);
12697 if (cps->protocolVersion == 1) {
12700 /* kludge: allow timeout for initial "feature" command */
12702 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12703 DisplayMessage("", buf);
12704 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12712 TwoMachinesEvent P((void))
12716 ChessProgramState *onmove;
12717 char *bookHit = NULL;
12718 static int stalling = 0;
12722 if (appData.noChessProgram) return;
12724 switch (gameMode) {
12725 case TwoMachinesPlay:
12727 case MachinePlaysWhite:
12728 case MachinePlaysBlack:
12729 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12730 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12734 case BeginningOfGame:
12735 case PlayFromGameFile:
12738 if (gameMode != EditGame) return;
12741 EditPositionDone(TRUE);
12752 // forwardMostMove = currentMove;
12753 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12755 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12757 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12758 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12759 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12763 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12764 SendToProgram("force\n", &second);
12766 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12769 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12770 if(appData.matchPause>10000 || appData.matchPause<10)
12771 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12772 wait = SubtractTimeMarks(&now, &pauseStart);
12773 if(wait < appData.matchPause) {
12774 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12778 DisplayMessage("", "");
12779 if (startedFromSetupPosition) {
12780 SendBoard(&second, backwardMostMove);
12781 if (appData.debugMode) {
12782 fprintf(debugFP, "Two Machines\n");
12785 for (i = backwardMostMove; i < forwardMostMove; i++) {
12786 SendMoveToProgram(i, &second);
12789 gameMode = TwoMachinesPlay;
12793 DisplayTwoMachinesTitle();
12795 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12800 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12801 SendToProgram(first.computerString, &first);
12802 if (first.sendName) {
12803 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12804 SendToProgram(buf, &first);
12806 SendToProgram(second.computerString, &second);
12807 if (second.sendName) {
12808 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12809 SendToProgram(buf, &second);
12813 if (!first.sendTime || !second.sendTime) {
12814 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12815 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12817 if (onmove->sendTime) {
12818 if (onmove->useColors) {
12819 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12821 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12823 if (onmove->useColors) {
12824 SendToProgram(onmove->twoMachinesColor, onmove);
12826 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12827 // SendToProgram("go\n", onmove);
12828 onmove->maybeThinking = TRUE;
12829 SetMachineThinkingEnables();
12833 if(bookHit) { // [HGM] book: simulate book reply
12834 static char bookMove[MSG_SIZ]; // a bit generous?
12836 programStats.nodes = programStats.depth = programStats.time =
12837 programStats.score = programStats.got_only_move = 0;
12838 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12840 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12841 strcat(bookMove, bookHit);
12842 savedMessage = bookMove; // args for deferred call
12843 savedState = onmove;
12844 ScheduleDelayedEvent(DeferredBookMove, 1);
12851 if (gameMode == Training) {
12852 SetTrainingModeOff();
12853 gameMode = PlayFromGameFile;
12854 DisplayMessage("", _("Training mode off"));
12856 gameMode = Training;
12857 animateTraining = appData.animate;
12859 /* make sure we are not already at the end of the game */
12860 if (currentMove < forwardMostMove) {
12861 SetTrainingModeOn();
12862 DisplayMessage("", _("Training mode on"));
12864 gameMode = PlayFromGameFile;
12865 DisplayError(_("Already at end of game"), 0);
12874 if (!appData.icsActive) return;
12875 switch (gameMode) {
12876 case IcsPlayingWhite:
12877 case IcsPlayingBlack:
12880 case BeginningOfGame:
12888 EditPositionDone(TRUE);
12901 gameMode = IcsIdle;
12912 switch (gameMode) {
12914 SetTrainingModeOff();
12916 case MachinePlaysWhite:
12917 case MachinePlaysBlack:
12918 case BeginningOfGame:
12919 SendToProgram("force\n", &first);
12920 SetUserThinkingEnables();
12922 case PlayFromGameFile:
12923 (void) StopLoadGameTimer();
12924 if (gameFileFP != NULL) {
12929 EditPositionDone(TRUE);
12934 SendToProgram("force\n", &first);
12936 case TwoMachinesPlay:
12937 GameEnds(EndOfFile, NULL, GE_PLAYER);
12938 ResurrectChessProgram();
12939 SetUserThinkingEnables();
12942 ResurrectChessProgram();
12944 case IcsPlayingBlack:
12945 case IcsPlayingWhite:
12946 DisplayError(_("Warning: You are still playing a game"), 0);
12949 DisplayError(_("Warning: You are still observing a game"), 0);
12952 DisplayError(_("Warning: You are still examining a game"), 0);
12963 first.offeredDraw = second.offeredDraw = 0;
12965 if (gameMode == PlayFromGameFile) {
12966 whiteTimeRemaining = timeRemaining[0][currentMove];
12967 blackTimeRemaining = timeRemaining[1][currentMove];
12971 if (gameMode == MachinePlaysWhite ||
12972 gameMode == MachinePlaysBlack ||
12973 gameMode == TwoMachinesPlay ||
12974 gameMode == EndOfGame) {
12975 i = forwardMostMove;
12976 while (i > currentMove) {
12977 SendToProgram("undo\n", &first);
12980 whiteTimeRemaining = timeRemaining[0][currentMove];
12981 blackTimeRemaining = timeRemaining[1][currentMove];
12982 DisplayBothClocks();
12983 if (whiteFlag || blackFlag) {
12984 whiteFlag = blackFlag = 0;
12989 gameMode = EditGame;
12996 EditPositionEvent()
12998 if (gameMode == EditPosition) {
13004 if (gameMode != EditGame) return;
13006 gameMode = EditPosition;
13009 if (currentMove > 0)
13010 CopyBoard(boards[0], boards[currentMove]);
13012 blackPlaysFirst = !WhiteOnMove(currentMove);
13014 currentMove = forwardMostMove = backwardMostMove = 0;
13015 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13022 /* [DM] icsEngineAnalyze - possible call from other functions */
13023 if (appData.icsEngineAnalyze) {
13024 appData.icsEngineAnalyze = FALSE;
13026 DisplayMessage("",_("Close ICS engine analyze..."));
13028 if (first.analysisSupport && first.analyzing) {
13029 SendToProgram("exit\n", &first);
13030 first.analyzing = FALSE;
13032 thinkOutput[0] = NULLCHAR;
13036 EditPositionDone(Boolean fakeRights)
13038 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13040 startedFromSetupPosition = TRUE;
13041 InitChessProgram(&first, FALSE);
13042 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13043 boards[0][EP_STATUS] = EP_NONE;
13044 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13045 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13046 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13047 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13048 } else boards[0][CASTLING][2] = NoRights;
13049 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13050 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13051 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13052 } else boards[0][CASTLING][5] = NoRights;
13054 SendToProgram("force\n", &first);
13055 if (blackPlaysFirst) {
13056 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13057 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13058 currentMove = forwardMostMove = backwardMostMove = 1;
13059 CopyBoard(boards[1], boards[0]);
13061 currentMove = forwardMostMove = backwardMostMove = 0;
13063 SendBoard(&first, forwardMostMove);
13064 if (appData.debugMode) {
13065 fprintf(debugFP, "EditPosDone\n");
13068 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13069 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13070 gameMode = EditGame;
13072 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13073 ClearHighlights(); /* [AS] */
13076 /* Pause for `ms' milliseconds */
13077 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13087 } while (SubtractTimeMarks(&m2, &m1) < ms);
13090 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13092 SendMultiLineToICS(buf)
13095 char temp[MSG_SIZ+1], *p;
13102 strncpy(temp, buf, len);
13107 if (*p == '\n' || *p == '\r')
13112 strcat(temp, "\n");
13114 SendToPlayer(temp, strlen(temp));
13118 SetWhiteToPlayEvent()
13120 if (gameMode == EditPosition) {
13121 blackPlaysFirst = FALSE;
13122 DisplayBothClocks(); /* works because currentMove is 0 */
13123 } else if (gameMode == IcsExamining) {
13124 SendToICS(ics_prefix);
13125 SendToICS("tomove white\n");
13130 SetBlackToPlayEvent()
13132 if (gameMode == EditPosition) {
13133 blackPlaysFirst = TRUE;
13134 currentMove = 1; /* kludge */
13135 DisplayBothClocks();
13137 } else if (gameMode == IcsExamining) {
13138 SendToICS(ics_prefix);
13139 SendToICS("tomove black\n");
13144 EditPositionMenuEvent(selection, x, y)
13145 ChessSquare selection;
13149 ChessSquare piece = boards[0][y][x];
13151 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13153 switch (selection) {
13155 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13156 SendToICS(ics_prefix);
13157 SendToICS("bsetup clear\n");
13158 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13159 SendToICS(ics_prefix);
13160 SendToICS("clearboard\n");
13162 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13163 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13164 for (y = 0; y < BOARD_HEIGHT; y++) {
13165 if (gameMode == IcsExamining) {
13166 if (boards[currentMove][y][x] != EmptySquare) {
13167 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13172 boards[0][y][x] = p;
13177 if (gameMode == EditPosition) {
13178 DrawPosition(FALSE, boards[0]);
13183 SetWhiteToPlayEvent();
13187 SetBlackToPlayEvent();
13191 if (gameMode == IcsExamining) {
13192 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13193 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13196 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13197 if(x == BOARD_LEFT-2) {
13198 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13199 boards[0][y][1] = 0;
13201 if(x == BOARD_RGHT+1) {
13202 if(y >= gameInfo.holdingsSize) break;
13203 boards[0][y][BOARD_WIDTH-2] = 0;
13206 boards[0][y][x] = EmptySquare;
13207 DrawPosition(FALSE, boards[0]);
13212 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13213 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13214 selection = (ChessSquare) (PROMOTED piece);
13215 } else if(piece == EmptySquare) selection = WhiteSilver;
13216 else selection = (ChessSquare)((int)piece - 1);
13220 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13221 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13222 selection = (ChessSquare) (DEMOTED piece);
13223 } else if(piece == EmptySquare) selection = BlackSilver;
13224 else selection = (ChessSquare)((int)piece + 1);
13229 if(gameInfo.variant == VariantShatranj ||
13230 gameInfo.variant == VariantXiangqi ||
13231 gameInfo.variant == VariantCourier ||
13232 gameInfo.variant == VariantMakruk )
13233 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13238 if(gameInfo.variant == VariantXiangqi)
13239 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13240 if(gameInfo.variant == VariantKnightmate)
13241 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13244 if (gameMode == IcsExamining) {
13245 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13246 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13247 PieceToChar(selection), AAA + x, ONE + y);
13250 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13252 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13253 n = PieceToNumber(selection - BlackPawn);
13254 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13255 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13256 boards[0][BOARD_HEIGHT-1-n][1]++;
13258 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13259 n = PieceToNumber(selection);
13260 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13261 boards[0][n][BOARD_WIDTH-1] = selection;
13262 boards[0][n][BOARD_WIDTH-2]++;
13265 boards[0][y][x] = selection;
13266 DrawPosition(TRUE, boards[0]);
13274 DropMenuEvent(selection, x, y)
13275 ChessSquare selection;
13278 ChessMove moveType;
13280 switch (gameMode) {
13281 case IcsPlayingWhite:
13282 case MachinePlaysBlack:
13283 if (!WhiteOnMove(currentMove)) {
13284 DisplayMoveError(_("It is Black's turn"));
13287 moveType = WhiteDrop;
13289 case IcsPlayingBlack:
13290 case MachinePlaysWhite:
13291 if (WhiteOnMove(currentMove)) {
13292 DisplayMoveError(_("It is White's turn"));
13295 moveType = BlackDrop;
13298 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13304 if (moveType == BlackDrop && selection < BlackPawn) {
13305 selection = (ChessSquare) ((int) selection
13306 + (int) BlackPawn - (int) WhitePawn);
13308 if (boards[currentMove][y][x] != EmptySquare) {
13309 DisplayMoveError(_("That square is occupied"));
13313 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13319 /* Accept a pending offer of any kind from opponent */
13321 if (appData.icsActive) {
13322 SendToICS(ics_prefix);
13323 SendToICS("accept\n");
13324 } else if (cmailMsgLoaded) {
13325 if (currentMove == cmailOldMove &&
13326 commentList[cmailOldMove] != NULL &&
13327 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13328 "Black offers a draw" : "White offers a draw")) {
13330 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13331 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13333 DisplayError(_("There is no pending offer on this move"), 0);
13334 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13337 /* Not used for offers from chess program */
13344 /* Decline a pending offer of any kind from opponent */
13346 if (appData.icsActive) {
13347 SendToICS(ics_prefix);
13348 SendToICS("decline\n");
13349 } else if (cmailMsgLoaded) {
13350 if (currentMove == cmailOldMove &&
13351 commentList[cmailOldMove] != NULL &&
13352 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13353 "Black offers a draw" : "White offers a draw")) {
13355 AppendComment(cmailOldMove, "Draw declined", TRUE);
13356 DisplayComment(cmailOldMove - 1, "Draw declined");
13359 DisplayError(_("There is no pending offer on this move"), 0);
13362 /* Not used for offers from chess program */
13369 /* Issue ICS rematch command */
13370 if (appData.icsActive) {
13371 SendToICS(ics_prefix);
13372 SendToICS("rematch\n");
13379 /* Call your opponent's flag (claim a win on time) */
13380 if (appData.icsActive) {
13381 SendToICS(ics_prefix);
13382 SendToICS("flag\n");
13384 switch (gameMode) {
13387 case MachinePlaysWhite:
13390 GameEnds(GameIsDrawn, "Both players ran out of time",
13393 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13395 DisplayError(_("Your opponent is not out of time"), 0);
13398 case MachinePlaysBlack:
13401 GameEnds(GameIsDrawn, "Both players ran out of time",
13404 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13406 DisplayError(_("Your opponent is not out of time"), 0);
13414 ClockClick(int which)
13415 { // [HGM] code moved to back-end from winboard.c
13416 if(which) { // black clock
13417 if (gameMode == EditPosition || gameMode == IcsExamining) {
13418 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13419 SetBlackToPlayEvent();
13420 } else if (gameMode == EditGame || shiftKey) {
13421 AdjustClock(which, -1);
13422 } else if (gameMode == IcsPlayingWhite ||
13423 gameMode == MachinePlaysBlack) {
13426 } else { // white clock
13427 if (gameMode == EditPosition || gameMode == IcsExamining) {
13428 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13429 SetWhiteToPlayEvent();
13430 } else if (gameMode == EditGame || shiftKey) {
13431 AdjustClock(which, -1);
13432 } else if (gameMode == IcsPlayingBlack ||
13433 gameMode == MachinePlaysWhite) {
13442 /* Offer draw or accept pending draw offer from opponent */
13444 if (appData.icsActive) {
13445 /* Note: tournament rules require draw offers to be
13446 made after you make your move but before you punch
13447 your clock. Currently ICS doesn't let you do that;
13448 instead, you immediately punch your clock after making
13449 a move, but you can offer a draw at any time. */
13451 SendToICS(ics_prefix);
13452 SendToICS("draw\n");
13453 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13454 } else if (cmailMsgLoaded) {
13455 if (currentMove == cmailOldMove &&
13456 commentList[cmailOldMove] != NULL &&
13457 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13458 "Black offers a draw" : "White offers a draw")) {
13459 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13460 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13461 } else if (currentMove == cmailOldMove + 1) {
13462 char *offer = WhiteOnMove(cmailOldMove) ?
13463 "White offers a draw" : "Black offers a draw";
13464 AppendComment(currentMove, offer, TRUE);
13465 DisplayComment(currentMove - 1, offer);
13466 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13468 DisplayError(_("You must make your move before offering a draw"), 0);
13469 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13471 } else if (first.offeredDraw) {
13472 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13474 if (first.sendDrawOffers) {
13475 SendToProgram("draw\n", &first);
13476 userOfferedDraw = TRUE;
13484 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13486 if (appData.icsActive) {
13487 SendToICS(ics_prefix);
13488 SendToICS("adjourn\n");
13490 /* Currently GNU Chess doesn't offer or accept Adjourns */
13498 /* Offer Abort or accept pending Abort offer from opponent */
13500 if (appData.icsActive) {
13501 SendToICS(ics_prefix);
13502 SendToICS("abort\n");
13504 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13511 /* Resign. You can do this even if it's not your turn. */
13513 if (appData.icsActive) {
13514 SendToICS(ics_prefix);
13515 SendToICS("resign\n");
13517 switch (gameMode) {
13518 case MachinePlaysWhite:
13519 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13521 case MachinePlaysBlack:
13522 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13525 if (cmailMsgLoaded) {
13527 if (WhiteOnMove(cmailOldMove)) {
13528 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13530 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13532 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13543 StopObservingEvent()
13545 /* Stop observing current games */
13546 SendToICS(ics_prefix);
13547 SendToICS("unobserve\n");
13551 StopExaminingEvent()
13553 /* Stop observing current game */
13554 SendToICS(ics_prefix);
13555 SendToICS("unexamine\n");
13559 ForwardInner(target)
13564 if (appData.debugMode)
13565 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13566 target, currentMove, forwardMostMove);
13568 if (gameMode == EditPosition)
13571 if (gameMode == PlayFromGameFile && !pausing)
13574 if (gameMode == IcsExamining && pausing)
13575 limit = pauseExamForwardMostMove;
13577 limit = forwardMostMove;
13579 if (target > limit) target = limit;
13581 if (target > 0 && moveList[target - 1][0]) {
13582 int fromX, fromY, toX, toY;
13583 toX = moveList[target - 1][2] - AAA;
13584 toY = moveList[target - 1][3] - ONE;
13585 if (moveList[target - 1][1] == '@') {
13586 if (appData.highlightLastMove) {
13587 SetHighlights(-1, -1, toX, toY);
13590 fromX = moveList[target - 1][0] - AAA;
13591 fromY = moveList[target - 1][1] - ONE;
13592 if (target == currentMove + 1) {
13593 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13595 if (appData.highlightLastMove) {
13596 SetHighlights(fromX, fromY, toX, toY);
13600 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13601 gameMode == Training || gameMode == PlayFromGameFile ||
13602 gameMode == AnalyzeFile) {
13603 while (currentMove < target) {
13604 SendMoveToProgram(currentMove++, &first);
13607 currentMove = target;
13610 if (gameMode == EditGame || gameMode == EndOfGame) {
13611 whiteTimeRemaining = timeRemaining[0][currentMove];
13612 blackTimeRemaining = timeRemaining[1][currentMove];
13614 DisplayBothClocks();
13615 DisplayMove(currentMove - 1);
13616 DrawPosition(FALSE, boards[currentMove]);
13617 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13618 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13619 DisplayComment(currentMove - 1, commentList[currentMove]);
13621 DisplayBook(currentMove);
13628 if (gameMode == IcsExamining && !pausing) {
13629 SendToICS(ics_prefix);
13630 SendToICS("forward\n");
13632 ForwardInner(currentMove + 1);
13639 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13640 /* to optimze, we temporarily turn off analysis mode while we feed
13641 * the remaining moves to the engine. Otherwise we get analysis output
13644 if (first.analysisSupport) {
13645 SendToProgram("exit\nforce\n", &first);
13646 first.analyzing = FALSE;
13650 if (gameMode == IcsExamining && !pausing) {
13651 SendToICS(ics_prefix);
13652 SendToICS("forward 999999\n");
13654 ForwardInner(forwardMostMove);
13657 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13658 /* we have fed all the moves, so reactivate analysis mode */
13659 SendToProgram("analyze\n", &first);
13660 first.analyzing = TRUE;
13661 /*first.maybeThinking = TRUE;*/
13662 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13667 BackwardInner(target)
13670 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13672 if (appData.debugMode)
13673 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13674 target, currentMove, forwardMostMove);
13676 if (gameMode == EditPosition) return;
13677 if (currentMove <= backwardMostMove) {
13679 DrawPosition(full_redraw, boards[currentMove]);
13682 if (gameMode == PlayFromGameFile && !pausing)
13685 if (moveList[target][0]) {
13686 int fromX, fromY, toX, toY;
13687 toX = moveList[target][2] - AAA;
13688 toY = moveList[target][3] - ONE;
13689 if (moveList[target][1] == '@') {
13690 if (appData.highlightLastMove) {
13691 SetHighlights(-1, -1, toX, toY);
13694 fromX = moveList[target][0] - AAA;
13695 fromY = moveList[target][1] - ONE;
13696 if (target == currentMove - 1) {
13697 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13699 if (appData.highlightLastMove) {
13700 SetHighlights(fromX, fromY, toX, toY);
13704 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13705 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13706 while (currentMove > target) {
13707 SendToProgram("undo\n", &first);
13711 currentMove = target;
13714 if (gameMode == EditGame || gameMode == EndOfGame) {
13715 whiteTimeRemaining = timeRemaining[0][currentMove];
13716 blackTimeRemaining = timeRemaining[1][currentMove];
13718 DisplayBothClocks();
13719 DisplayMove(currentMove - 1);
13720 DrawPosition(full_redraw, boards[currentMove]);
13721 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13722 // [HGM] PV info: routine tests if comment empty
13723 DisplayComment(currentMove - 1, commentList[currentMove]);
13724 DisplayBook(currentMove);
13730 if (gameMode == IcsExamining && !pausing) {
13731 SendToICS(ics_prefix);
13732 SendToICS("backward\n");
13734 BackwardInner(currentMove - 1);
13741 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13742 /* to optimize, we temporarily turn off analysis mode while we undo
13743 * all the moves. Otherwise we get analysis output after each undo.
13745 if (first.analysisSupport) {
13746 SendToProgram("exit\nforce\n", &first);
13747 first.analyzing = FALSE;
13751 if (gameMode == IcsExamining && !pausing) {
13752 SendToICS(ics_prefix);
13753 SendToICS("backward 999999\n");
13755 BackwardInner(backwardMostMove);
13758 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13759 /* we have fed all the moves, so reactivate analysis mode */
13760 SendToProgram("analyze\n", &first);
13761 first.analyzing = TRUE;
13762 /*first.maybeThinking = TRUE;*/
13763 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13770 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13771 if (to >= forwardMostMove) to = forwardMostMove;
13772 if (to <= backwardMostMove) to = backwardMostMove;
13773 if (to < currentMove) {
13781 RevertEvent(Boolean annotate)
13783 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13786 if (gameMode != IcsExamining) {
13787 DisplayError(_("You are not examining a game"), 0);
13791 DisplayError(_("You can't revert while pausing"), 0);
13794 SendToICS(ics_prefix);
13795 SendToICS("revert\n");
13801 switch (gameMode) {
13802 case MachinePlaysWhite:
13803 case MachinePlaysBlack:
13804 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13805 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13808 if (forwardMostMove < 2) return;
13809 currentMove = forwardMostMove = forwardMostMove - 2;
13810 whiteTimeRemaining = timeRemaining[0][currentMove];
13811 blackTimeRemaining = timeRemaining[1][currentMove];
13812 DisplayBothClocks();
13813 DisplayMove(currentMove - 1);
13814 ClearHighlights();/*!! could figure this out*/
13815 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13816 SendToProgram("remove\n", &first);
13817 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13820 case BeginningOfGame:
13824 case IcsPlayingWhite:
13825 case IcsPlayingBlack:
13826 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13827 SendToICS(ics_prefix);
13828 SendToICS("takeback 2\n");
13830 SendToICS(ics_prefix);
13831 SendToICS("takeback 1\n");
13840 ChessProgramState *cps;
13842 switch (gameMode) {
13843 case MachinePlaysWhite:
13844 if (!WhiteOnMove(forwardMostMove)) {
13845 DisplayError(_("It is your turn"), 0);
13850 case MachinePlaysBlack:
13851 if (WhiteOnMove(forwardMostMove)) {
13852 DisplayError(_("It is your turn"), 0);
13857 case TwoMachinesPlay:
13858 if (WhiteOnMove(forwardMostMove) ==
13859 (first.twoMachinesColor[0] == 'w')) {
13865 case BeginningOfGame:
13869 SendToProgram("?\n", cps);
13873 TruncateGameEvent()
13876 if (gameMode != EditGame) return;
13883 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13884 if (forwardMostMove > currentMove) {
13885 if (gameInfo.resultDetails != NULL) {
13886 free(gameInfo.resultDetails);
13887 gameInfo.resultDetails = NULL;
13888 gameInfo.result = GameUnfinished;
13890 forwardMostMove = currentMove;
13891 HistorySet(parseList, backwardMostMove, forwardMostMove,
13899 if (appData.noChessProgram) return;
13900 switch (gameMode) {
13901 case MachinePlaysWhite:
13902 if (WhiteOnMove(forwardMostMove)) {
13903 DisplayError(_("Wait until your turn"), 0);
13907 case BeginningOfGame:
13908 case MachinePlaysBlack:
13909 if (!WhiteOnMove(forwardMostMove)) {
13910 DisplayError(_("Wait until your turn"), 0);
13915 DisplayError(_("No hint available"), 0);
13918 SendToProgram("hint\n", &first);
13919 hintRequested = TRUE;
13925 if (appData.noChessProgram) return;
13926 switch (gameMode) {
13927 case MachinePlaysWhite:
13928 if (WhiteOnMove(forwardMostMove)) {
13929 DisplayError(_("Wait until your turn"), 0);
13933 case BeginningOfGame:
13934 case MachinePlaysBlack:
13935 if (!WhiteOnMove(forwardMostMove)) {
13936 DisplayError(_("Wait until your turn"), 0);
13941 EditPositionDone(TRUE);
13943 case TwoMachinesPlay:
13948 SendToProgram("bk\n", &first);
13949 bookOutput[0] = NULLCHAR;
13950 bookRequested = TRUE;
13956 char *tags = PGNTags(&gameInfo);
13957 TagsPopUp(tags, CmailMsg());
13961 /* end button procedures */
13964 PrintPosition(fp, move)
13970 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13971 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13972 char c = PieceToChar(boards[move][i][j]);
13973 fputc(c == 'x' ? '.' : c, fp);
13974 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13977 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13978 fprintf(fp, "white to play\n");
13980 fprintf(fp, "black to play\n");
13987 if (gameInfo.white != NULL) {
13988 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13994 /* Find last component of program's own name, using some heuristics */
13996 TidyProgramName(prog, host, buf)
13997 char *prog, *host, buf[MSG_SIZ];
14000 int local = (strcmp(host, "localhost") == 0);
14001 while (!local && (p = strchr(prog, ';')) != NULL) {
14003 while (*p == ' ') p++;
14006 if (*prog == '"' || *prog == '\'') {
14007 q = strchr(prog + 1, *prog);
14009 q = strchr(prog, ' ');
14011 if (q == NULL) q = prog + strlen(prog);
14013 while (p >= prog && *p != '/' && *p != '\\') p--;
14015 if(p == prog && *p == '"') p++;
14016 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14017 memcpy(buf, p, q - p);
14018 buf[q - p] = NULLCHAR;
14026 TimeControlTagValue()
14029 if (!appData.clockMode) {
14030 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14031 } else if (movesPerSession > 0) {
14032 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14033 } else if (timeIncrement == 0) {
14034 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14036 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14038 return StrSave(buf);
14044 /* This routine is used only for certain modes */
14045 VariantClass v = gameInfo.variant;
14046 ChessMove r = GameUnfinished;
14049 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14050 r = gameInfo.result;
14051 p = gameInfo.resultDetails;
14052 gameInfo.resultDetails = NULL;
14054 ClearGameInfo(&gameInfo);
14055 gameInfo.variant = v;
14057 switch (gameMode) {
14058 case MachinePlaysWhite:
14059 gameInfo.event = StrSave( appData.pgnEventHeader );
14060 gameInfo.site = StrSave(HostName());
14061 gameInfo.date = PGNDate();
14062 gameInfo.round = StrSave("-");
14063 gameInfo.white = StrSave(first.tidy);
14064 gameInfo.black = StrSave(UserName());
14065 gameInfo.timeControl = TimeControlTagValue();
14068 case MachinePlaysBlack:
14069 gameInfo.event = StrSave( appData.pgnEventHeader );
14070 gameInfo.site = StrSave(HostName());
14071 gameInfo.date = PGNDate();
14072 gameInfo.round = StrSave("-");
14073 gameInfo.white = StrSave(UserName());
14074 gameInfo.black = StrSave(first.tidy);
14075 gameInfo.timeControl = TimeControlTagValue();
14078 case TwoMachinesPlay:
14079 gameInfo.event = StrSave( appData.pgnEventHeader );
14080 gameInfo.site = StrSave(HostName());
14081 gameInfo.date = PGNDate();
14084 snprintf(buf, MSG_SIZ, "%d", roundNr);
14085 gameInfo.round = StrSave(buf);
14087 gameInfo.round = StrSave("-");
14089 if (first.twoMachinesColor[0] == 'w') {
14090 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14091 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14093 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14094 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14096 gameInfo.timeControl = TimeControlTagValue();
14100 gameInfo.event = StrSave("Edited game");
14101 gameInfo.site = StrSave(HostName());
14102 gameInfo.date = PGNDate();
14103 gameInfo.round = StrSave("-");
14104 gameInfo.white = StrSave("-");
14105 gameInfo.black = StrSave("-");
14106 gameInfo.result = r;
14107 gameInfo.resultDetails = p;
14111 gameInfo.event = StrSave("Edited position");
14112 gameInfo.site = StrSave(HostName());
14113 gameInfo.date = PGNDate();
14114 gameInfo.round = StrSave("-");
14115 gameInfo.white = StrSave("-");
14116 gameInfo.black = StrSave("-");
14119 case IcsPlayingWhite:
14120 case IcsPlayingBlack:
14125 case PlayFromGameFile:
14126 gameInfo.event = StrSave("Game from non-PGN file");
14127 gameInfo.site = StrSave(HostName());
14128 gameInfo.date = PGNDate();
14129 gameInfo.round = StrSave("-");
14130 gameInfo.white = StrSave("?");
14131 gameInfo.black = StrSave("?");
14140 ReplaceComment(index, text)
14148 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14149 pvInfoList[index-1].depth == len &&
14150 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14151 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14152 while (*text == '\n') text++;
14153 len = strlen(text);
14154 while (len > 0 && text[len - 1] == '\n') len--;
14156 if (commentList[index] != NULL)
14157 free(commentList[index]);
14160 commentList[index] = NULL;
14163 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14164 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14165 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14166 commentList[index] = (char *) malloc(len + 2);
14167 strncpy(commentList[index], text, len);
14168 commentList[index][len] = '\n';
14169 commentList[index][len + 1] = NULLCHAR;
14171 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14173 commentList[index] = (char *) malloc(len + 7);
14174 safeStrCpy(commentList[index], "{\n", 3);
14175 safeStrCpy(commentList[index]+2, text, len+1);
14176 commentList[index][len+2] = NULLCHAR;
14177 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14178 strcat(commentList[index], "\n}\n");
14192 if (ch == '\r') continue;
14194 } while (ch != '\0');
14198 AppendComment(index, text, addBraces)
14201 Boolean addBraces; // [HGM] braces: tells if we should add {}
14206 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14207 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14210 while (*text == '\n') text++;
14211 len = strlen(text);
14212 while (len > 0 && text[len - 1] == '\n') len--;
14214 if (len == 0) return;
14216 if (commentList[index] != NULL) {
14217 old = commentList[index];
14218 oldlen = strlen(old);
14219 while(commentList[index][oldlen-1] == '\n')
14220 commentList[index][--oldlen] = NULLCHAR;
14221 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14222 safeStrCpy(commentList[index], old, oldlen + len + 6);
14224 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14225 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14226 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14227 while (*text == '\n') { text++; len--; }
14228 commentList[index][--oldlen] = NULLCHAR;
14230 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14231 else strcat(commentList[index], "\n");
14232 strcat(commentList[index], text);
14233 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14234 else strcat(commentList[index], "\n");
14236 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14238 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14239 else commentList[index][0] = NULLCHAR;
14240 strcat(commentList[index], text);
14241 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14242 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14246 static char * FindStr( char * text, char * sub_text )
14248 char * result = strstr( text, sub_text );
14250 if( result != NULL ) {
14251 result += strlen( sub_text );
14257 /* [AS] Try to extract PV info from PGN comment */
14258 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14259 char *GetInfoFromComment( int index, char * text )
14261 char * sep = text, *p;
14263 if( text != NULL && index > 0 ) {
14266 int time = -1, sec = 0, deci;
14267 char * s_eval = FindStr( text, "[%eval " );
14268 char * s_emt = FindStr( text, "[%emt " );
14270 if( s_eval != NULL || s_emt != NULL ) {
14274 if( s_eval != NULL ) {
14275 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14279 if( delim != ']' ) {
14284 if( s_emt != NULL ) {
14289 /* We expect something like: [+|-]nnn.nn/dd */
14292 if(*text != '{') return text; // [HGM] braces: must be normal comment
14294 sep = strchr( text, '/' );
14295 if( sep == NULL || sep < (text+4) ) {
14300 if(p[1] == '(') { // comment starts with PV
14301 p = strchr(p, ')'); // locate end of PV
14302 if(p == NULL || sep < p+5) return text;
14303 // at this point we have something like "{(.*) +0.23/6 ..."
14304 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14305 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14306 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14308 time = -1; sec = -1; deci = -1;
14309 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14310 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14311 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14312 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14316 if( score_lo < 0 || score_lo >= 100 ) {
14320 if(sec >= 0) time = 600*time + 10*sec; else
14321 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14323 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14325 /* [HGM] PV time: now locate end of PV info */
14326 while( *++sep >= '0' && *sep <= '9'); // strip depth
14328 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14330 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14332 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14333 while(*sep == ' ') sep++;
14344 pvInfoList[index-1].depth = depth;
14345 pvInfoList[index-1].score = score;
14346 pvInfoList[index-1].time = 10*time; // centi-sec
14347 if(*sep == '}') *sep = 0; else *--sep = '{';
14348 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14354 SendToProgram(message, cps)
14356 ChessProgramState *cps;
14358 int count, outCount, error;
14361 if (cps->pr == NULL) return;
14364 if (appData.debugMode) {
14367 fprintf(debugFP, "%ld >%-6s: %s",
14368 SubtractTimeMarks(&now, &programStartTime),
14369 cps->which, message);
14372 count = strlen(message);
14373 outCount = OutputToProcess(cps->pr, message, count, &error);
14374 if (outCount < count && !exiting
14375 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14376 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14377 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14378 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14379 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14380 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14381 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14382 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14384 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14385 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14386 gameInfo.result = res;
14388 gameInfo.resultDetails = StrSave(buf);
14390 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14391 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14396 ReceiveFromProgram(isr, closure, message, count, error)
14397 InputSourceRef isr;
14405 ChessProgramState *cps = (ChessProgramState *)closure;
14407 if (isr != cps->isr) return; /* Killed intentionally */
14410 RemoveInputSource(cps->isr);
14411 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14412 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14413 _(cps->which), cps->program);
14414 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14415 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14416 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14417 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14418 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14420 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14421 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14422 gameInfo.result = res;
14424 gameInfo.resultDetails = StrSave(buf);
14426 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14427 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14429 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14430 _(cps->which), cps->program);
14431 RemoveInputSource(cps->isr);
14433 /* [AS] Program is misbehaving badly... kill it */
14434 if( count == -2 ) {
14435 DestroyChildProcess( cps->pr, 9 );
14439 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14444 if ((end_str = strchr(message, '\r')) != NULL)
14445 *end_str = NULLCHAR;
14446 if ((end_str = strchr(message, '\n')) != NULL)
14447 *end_str = NULLCHAR;
14449 if (appData.debugMode) {
14450 TimeMark now; int print = 1;
14451 char *quote = ""; char c; int i;
14453 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14454 char start = message[0];
14455 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14456 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14457 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14458 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14459 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14460 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14461 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14462 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14463 sscanf(message, "hint: %c", &c)!=1 &&
14464 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14465 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14466 print = (appData.engineComments >= 2);
14468 message[0] = start; // restore original message
14472 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14473 SubtractTimeMarks(&now, &programStartTime), cps->which,
14479 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14480 if (appData.icsEngineAnalyze) {
14481 if (strstr(message, "whisper") != NULL ||
14482 strstr(message, "kibitz") != NULL ||
14483 strstr(message, "tellics") != NULL) return;
14486 HandleMachineMove(message, cps);
14491 SendTimeControl(cps, mps, tc, inc, sd, st)
14492 ChessProgramState *cps;
14493 int mps, inc, sd, st;
14499 if( timeControl_2 > 0 ) {
14500 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14501 tc = timeControl_2;
14504 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14505 inc /= cps->timeOdds;
14506 st /= cps->timeOdds;
14508 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14511 /* Set exact time per move, normally using st command */
14512 if (cps->stKludge) {
14513 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14515 if (seconds == 0) {
14516 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14518 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14521 snprintf(buf, MSG_SIZ, "st %d\n", st);
14524 /* Set conventional or incremental time control, using level command */
14525 if (seconds == 0) {
14526 /* Note old gnuchess bug -- minutes:seconds used to not work.
14527 Fixed in later versions, but still avoid :seconds
14528 when seconds is 0. */
14529 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14531 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14532 seconds, inc/1000.);
14535 SendToProgram(buf, cps);
14537 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14538 /* Orthogonally, limit search to given depth */
14540 if (cps->sdKludge) {
14541 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14543 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14545 SendToProgram(buf, cps);
14548 if(cps->nps >= 0) { /* [HGM] nps */
14549 if(cps->supportsNPS == FALSE)
14550 cps->nps = -1; // don't use if engine explicitly says not supported!
14552 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14553 SendToProgram(buf, cps);
14558 ChessProgramState *WhitePlayer()
14559 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14561 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14562 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14568 SendTimeRemaining(cps, machineWhite)
14569 ChessProgramState *cps;
14570 int /*boolean*/ machineWhite;
14572 char message[MSG_SIZ];
14575 /* Note: this routine must be called when the clocks are stopped
14576 or when they have *just* been set or switched; otherwise
14577 it will be off by the time since the current tick started.
14579 if (machineWhite) {
14580 time = whiteTimeRemaining / 10;
14581 otime = blackTimeRemaining / 10;
14583 time = blackTimeRemaining / 10;
14584 otime = whiteTimeRemaining / 10;
14586 /* [HGM] translate opponent's time by time-odds factor */
14587 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14588 if (appData.debugMode) {
14589 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14592 if (time <= 0) time = 1;
14593 if (otime <= 0) otime = 1;
14595 snprintf(message, MSG_SIZ, "time %ld\n", time);
14596 SendToProgram(message, cps);
14598 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14599 SendToProgram(message, cps);
14603 BoolFeature(p, name, loc, cps)
14607 ChessProgramState *cps;
14610 int len = strlen(name);
14613 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14615 sscanf(*p, "%d", &val);
14617 while (**p && **p != ' ')
14619 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14620 SendToProgram(buf, cps);
14627 IntFeature(p, name, loc, cps)
14631 ChessProgramState *cps;
14634 int len = strlen(name);
14635 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14637 sscanf(*p, "%d", loc);
14638 while (**p && **p != ' ') (*p)++;
14639 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14640 SendToProgram(buf, cps);
14647 StringFeature(p, name, loc, cps)
14651 ChessProgramState *cps;
14654 int len = strlen(name);
14655 if (strncmp((*p), name, len) == 0
14656 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14658 sscanf(*p, "%[^\"]", loc);
14659 while (**p && **p != '\"') (*p)++;
14660 if (**p == '\"') (*p)++;
14661 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14662 SendToProgram(buf, cps);
14669 ParseOption(Option *opt, ChessProgramState *cps)
14670 // [HGM] options: process the string that defines an engine option, and determine
14671 // name, type, default value, and allowed value range
14673 char *p, *q, buf[MSG_SIZ];
14674 int n, min = (-1)<<31, max = 1<<31, def;
14676 if(p = strstr(opt->name, " -spin ")) {
14677 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14678 if(max < min) max = min; // enforce consistency
14679 if(def < min) def = min;
14680 if(def > max) def = max;
14685 } else if((p = strstr(opt->name, " -slider "))) {
14686 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14687 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14688 if(max < min) max = min; // enforce consistency
14689 if(def < min) def = min;
14690 if(def > max) def = max;
14694 opt->type = Spin; // Slider;
14695 } else if((p = strstr(opt->name, " -string "))) {
14696 opt->textValue = p+9;
14697 opt->type = TextBox;
14698 } else if((p = strstr(opt->name, " -file "))) {
14699 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14700 opt->textValue = p+7;
14701 opt->type = FileName; // FileName;
14702 } else if((p = strstr(opt->name, " -path "))) {
14703 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14704 opt->textValue = p+7;
14705 opt->type = PathName; // PathName;
14706 } else if(p = strstr(opt->name, " -check ")) {
14707 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14708 opt->value = (def != 0);
14709 opt->type = CheckBox;
14710 } else if(p = strstr(opt->name, " -combo ")) {
14711 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14712 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14713 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14714 opt->value = n = 0;
14715 while(q = StrStr(q, " /// ")) {
14716 n++; *q = 0; // count choices, and null-terminate each of them
14718 if(*q == '*') { // remember default, which is marked with * prefix
14722 cps->comboList[cps->comboCnt++] = q;
14724 cps->comboList[cps->comboCnt++] = NULL;
14726 opt->type = ComboBox;
14727 } else if(p = strstr(opt->name, " -button")) {
14728 opt->type = Button;
14729 } else if(p = strstr(opt->name, " -save")) {
14730 opt->type = SaveButton;
14731 } else return FALSE;
14732 *p = 0; // terminate option name
14733 // now look if the command-line options define a setting for this engine option.
14734 if(cps->optionSettings && cps->optionSettings[0])
14735 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14736 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14737 snprintf(buf, MSG_SIZ, "option %s", p);
14738 if(p = strstr(buf, ",")) *p = 0;
14739 if(q = strchr(buf, '=')) switch(opt->type) {
14741 for(n=0; n<opt->max; n++)
14742 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14745 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14749 opt->value = atoi(q+1);
14754 SendToProgram(buf, cps);
14760 FeatureDone(cps, val)
14761 ChessProgramState* cps;
14764 DelayedEventCallback cb = GetDelayedEvent();
14765 if ((cb == InitBackEnd3 && cps == &first) ||
14766 (cb == SettingsMenuIfReady && cps == &second) ||
14767 (cb == LoadEngine) ||
14768 (cb == TwoMachinesEventIfReady)) {
14769 CancelDelayedEvent();
14770 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14772 cps->initDone = val;
14775 /* Parse feature command from engine */
14777 ParseFeatures(args, cps)
14779 ChessProgramState *cps;
14787 while (*p == ' ') p++;
14788 if (*p == NULLCHAR) return;
14790 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14791 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14792 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14793 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14794 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14795 if (BoolFeature(&p, "reuse", &val, cps)) {
14796 /* Engine can disable reuse, but can't enable it if user said no */
14797 if (!val) cps->reuse = FALSE;
14800 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14801 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14802 if (gameMode == TwoMachinesPlay) {
14803 DisplayTwoMachinesTitle();
14809 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14810 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14811 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14812 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14813 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14814 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14815 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14816 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14817 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14818 if (IntFeature(&p, "done", &val, cps)) {
14819 FeatureDone(cps, val);
14822 /* Added by Tord: */
14823 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14824 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14825 /* End of additions by Tord */
14827 /* [HGM] added features: */
14828 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14829 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14830 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14831 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14832 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14833 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14834 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14835 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14836 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14837 SendToProgram(buf, cps);
14840 if(cps->nrOptions >= MAX_OPTIONS) {
14842 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14843 DisplayError(buf, 0);
14847 /* End of additions by HGM */
14849 /* unknown feature: complain and skip */
14851 while (*q && *q != '=') q++;
14852 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14853 SendToProgram(buf, cps);
14859 while (*p && *p != '\"') p++;
14860 if (*p == '\"') p++;
14862 while (*p && *p != ' ') p++;
14870 PeriodicUpdatesEvent(newState)
14873 if (newState == appData.periodicUpdates)
14876 appData.periodicUpdates=newState;
14878 /* Display type changes, so update it now */
14879 // DisplayAnalysis();
14881 /* Get the ball rolling again... */
14883 AnalysisPeriodicEvent(1);
14884 StartAnalysisClock();
14889 PonderNextMoveEvent(newState)
14892 if (newState == appData.ponderNextMove) return;
14893 if (gameMode == EditPosition) EditPositionDone(TRUE);
14895 SendToProgram("hard\n", &first);
14896 if (gameMode == TwoMachinesPlay) {
14897 SendToProgram("hard\n", &second);
14900 SendToProgram("easy\n", &first);
14901 thinkOutput[0] = NULLCHAR;
14902 if (gameMode == TwoMachinesPlay) {
14903 SendToProgram("easy\n", &second);
14906 appData.ponderNextMove = newState;
14910 NewSettingEvent(option, feature, command, value)
14912 int option, value, *feature;
14916 if (gameMode == EditPosition) EditPositionDone(TRUE);
14917 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14918 if(feature == NULL || *feature) SendToProgram(buf, &first);
14919 if (gameMode == TwoMachinesPlay) {
14920 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14925 ShowThinkingEvent()
14926 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14928 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14929 int newState = appData.showThinking
14930 // [HGM] thinking: other features now need thinking output as well
14931 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14933 if (oldState == newState) return;
14934 oldState = newState;
14935 if (gameMode == EditPosition) EditPositionDone(TRUE);
14937 SendToProgram("post\n", &first);
14938 if (gameMode == TwoMachinesPlay) {
14939 SendToProgram("post\n", &second);
14942 SendToProgram("nopost\n", &first);
14943 thinkOutput[0] = NULLCHAR;
14944 if (gameMode == TwoMachinesPlay) {
14945 SendToProgram("nopost\n", &second);
14948 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14952 AskQuestionEvent(title, question, replyPrefix, which)
14953 char *title; char *question; char *replyPrefix; char *which;
14955 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14956 if (pr == NoProc) return;
14957 AskQuestion(title, question, replyPrefix, pr);
14961 TypeInEvent(char firstChar)
14963 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14964 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14965 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14966 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14967 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14968 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14969 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14970 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14971 gameMode == Training) PopUpMoveDialog(firstChar);
14975 TypeInDoneEvent(char *move)
14978 int n, fromX, fromY, toX, toY;
14980 ChessMove moveType;
\r
14983 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14984 EditPositionPasteFEN(move);
\r
14987 // [HGM] movenum: allow move number to be typed in any mode
\r
14988 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14989 ToNrEvent(2*n-1);
\r
14993 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14994 gameMode != Training) {
\r
14995 DisplayMoveError(_("Displayed move is not current"));
\r
14997 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14998 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14999 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
15000 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15001 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
15002 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
15004 DisplayMoveError(_("Could not parse move"));
\r
15010 DisplayMove(moveNumber)
15013 char message[MSG_SIZ];
15015 char cpThinkOutput[MSG_SIZ];
15017 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15019 if (moveNumber == forwardMostMove - 1 ||
15020 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15022 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15024 if (strchr(cpThinkOutput, '\n')) {
15025 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15028 *cpThinkOutput = NULLCHAR;
15031 /* [AS] Hide thinking from human user */
15032 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15033 *cpThinkOutput = NULLCHAR;
15034 if( thinkOutput[0] != NULLCHAR ) {
15037 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15038 cpThinkOutput[i] = '.';
15040 cpThinkOutput[i] = NULLCHAR;
15041 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15045 if (moveNumber == forwardMostMove - 1 &&
15046 gameInfo.resultDetails != NULL) {
15047 if (gameInfo.resultDetails[0] == NULLCHAR) {
15048 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15050 snprintf(res, MSG_SIZ, " {%s} %s",
15051 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15057 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15058 DisplayMessage(res, cpThinkOutput);
15060 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15061 WhiteOnMove(moveNumber) ? " " : ".. ",
15062 parseList[moveNumber], res);
15063 DisplayMessage(message, cpThinkOutput);
15068 DisplayComment(moveNumber, text)
15072 char title[MSG_SIZ];
15073 char buf[8000]; // comment can be long!
15076 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15077 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15079 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15080 WhiteOnMove(moveNumber) ? " " : ".. ",
15081 parseList[moveNumber]);
15083 // [HGM] PV info: display PV info together with (or as) comment
15084 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15085 if(text == NULL) text = "";
15086 score = pvInfoList[moveNumber].score;
15087 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15088 depth, (pvInfoList[moveNumber].time+50)/100, text);
15091 if (text != NULL && (appData.autoDisplayComment || commentUp))
15092 CommentPopUp(title, text);
15095 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15096 * might be busy thinking or pondering. It can be omitted if your
15097 * gnuchess is configured to stop thinking immediately on any user
15098 * input. However, that gnuchess feature depends on the FIONREAD
15099 * ioctl, which does not work properly on some flavors of Unix.
15103 ChessProgramState *cps;
15106 if (!cps->useSigint) return;
15107 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15108 switch (gameMode) {
15109 case MachinePlaysWhite:
15110 case MachinePlaysBlack:
15111 case TwoMachinesPlay:
15112 case IcsPlayingWhite:
15113 case IcsPlayingBlack:
15116 /* Skip if we know it isn't thinking */
15117 if (!cps->maybeThinking) return;
15118 if (appData.debugMode)
15119 fprintf(debugFP, "Interrupting %s\n", cps->which);
15120 InterruptChildProcess(cps->pr);
15121 cps->maybeThinking = FALSE;
15126 #endif /*ATTENTION*/
15132 if (whiteTimeRemaining <= 0) {
15135 if (appData.icsActive) {
15136 if (appData.autoCallFlag &&
15137 gameMode == IcsPlayingBlack && !blackFlag) {
15138 SendToICS(ics_prefix);
15139 SendToICS("flag\n");
15143 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15145 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15146 if (appData.autoCallFlag) {
15147 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15154 if (blackTimeRemaining <= 0) {
15157 if (appData.icsActive) {
15158 if (appData.autoCallFlag &&
15159 gameMode == IcsPlayingWhite && !whiteFlag) {
15160 SendToICS(ics_prefix);
15161 SendToICS("flag\n");
15165 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15167 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15168 if (appData.autoCallFlag) {
15169 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15182 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15183 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15186 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15188 if ( !WhiteOnMove(forwardMostMove) ) {
15189 /* White made time control */
15190 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15191 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15192 /* [HGM] time odds: correct new time quota for time odds! */
15193 / WhitePlayer()->timeOdds;
15194 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15196 lastBlack -= blackTimeRemaining;
15197 /* Black made time control */
15198 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15199 / WhitePlayer()->other->timeOdds;
15200 lastWhite = whiteTimeRemaining;
15205 DisplayBothClocks()
15207 int wom = gameMode == EditPosition ?
15208 !blackPlaysFirst : WhiteOnMove(currentMove);
15209 DisplayWhiteClock(whiteTimeRemaining, wom);
15210 DisplayBlackClock(blackTimeRemaining, !wom);
15214 /* Timekeeping seems to be a portability nightmare. I think everyone
15215 has ftime(), but I'm really not sure, so I'm including some ifdefs
15216 to use other calls if you don't. Clocks will be less accurate if
15217 you have neither ftime nor gettimeofday.
15220 /* VS 2008 requires the #include outside of the function */
15221 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15222 #include <sys/timeb.h>
15225 /* Get the current time as a TimeMark */
15230 #if HAVE_GETTIMEOFDAY
15232 struct timeval timeVal;
15233 struct timezone timeZone;
15235 gettimeofday(&timeVal, &timeZone);
15236 tm->sec = (long) timeVal.tv_sec;
15237 tm->ms = (int) (timeVal.tv_usec / 1000L);
15239 #else /*!HAVE_GETTIMEOFDAY*/
15242 // include <sys/timeb.h> / moved to just above start of function
15243 struct timeb timeB;
15246 tm->sec = (long) timeB.time;
15247 tm->ms = (int) timeB.millitm;
15249 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15250 tm->sec = (long) time(NULL);
15256 /* Return the difference in milliseconds between two
15257 time marks. We assume the difference will fit in a long!
15260 SubtractTimeMarks(tm2, tm1)
15261 TimeMark *tm2, *tm1;
15263 return 1000L*(tm2->sec - tm1->sec) +
15264 (long) (tm2->ms - tm1->ms);
15269 * Code to manage the game clocks.
15271 * In tournament play, black starts the clock and then white makes a move.
15272 * We give the human user a slight advantage if he is playing white---the
15273 * clocks don't run until he makes his first move, so it takes zero time.
15274 * Also, we don't account for network lag, so we could get out of sync
15275 * with GNU Chess's clock -- but then, referees are always right.
15278 static TimeMark tickStartTM;
15279 static long intendedTickLength;
15282 NextTickLength(timeRemaining)
15283 long timeRemaining;
15285 long nominalTickLength, nextTickLength;
15287 if (timeRemaining > 0L && timeRemaining <= 10000L)
15288 nominalTickLength = 100L;
15290 nominalTickLength = 1000L;
15291 nextTickLength = timeRemaining % nominalTickLength;
15292 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15294 return nextTickLength;
15297 /* Adjust clock one minute up or down */
15299 AdjustClock(Boolean which, int dir)
15301 if(which) blackTimeRemaining += 60000*dir;
15302 else whiteTimeRemaining += 60000*dir;
15303 DisplayBothClocks();
15306 /* Stop clocks and reset to a fresh time control */
15310 (void) StopClockTimer();
15311 if (appData.icsActive) {
15312 whiteTimeRemaining = blackTimeRemaining = 0;
15313 } else if (searchTime) {
15314 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15315 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15316 } else { /* [HGM] correct new time quote for time odds */
15317 whiteTC = blackTC = fullTimeControlString;
15318 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15319 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15321 if (whiteFlag || blackFlag) {
15323 whiteFlag = blackFlag = FALSE;
15325 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15326 DisplayBothClocks();
15329 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15331 /* Decrement running clock by amount of time that has passed */
15335 long timeRemaining;
15336 long lastTickLength, fudge;
15339 if (!appData.clockMode) return;
15340 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15344 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15346 /* Fudge if we woke up a little too soon */
15347 fudge = intendedTickLength - lastTickLength;
15348 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15350 if (WhiteOnMove(forwardMostMove)) {
15351 if(whiteNPS >= 0) lastTickLength = 0;
15352 timeRemaining = whiteTimeRemaining -= lastTickLength;
15353 if(timeRemaining < 0 && !appData.icsActive) {
15354 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15355 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15356 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15357 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15360 DisplayWhiteClock(whiteTimeRemaining - fudge,
15361 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15363 if(blackNPS >= 0) lastTickLength = 0;
15364 timeRemaining = blackTimeRemaining -= lastTickLength;
15365 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15366 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15368 blackStartMove = forwardMostMove;
15369 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15372 DisplayBlackClock(blackTimeRemaining - fudge,
15373 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15375 if (CheckFlags()) return;
15378 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15379 StartClockTimer(intendedTickLength);
15381 /* if the time remaining has fallen below the alarm threshold, sound the
15382 * alarm. if the alarm has sounded and (due to a takeback or time control
15383 * with increment) the time remaining has increased to a level above the
15384 * threshold, reset the alarm so it can sound again.
15387 if (appData.icsActive && appData.icsAlarm) {
15389 /* make sure we are dealing with the user's clock */
15390 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15391 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15394 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15395 alarmSounded = FALSE;
15396 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15398 alarmSounded = TRUE;
15404 /* A player has just moved, so stop the previously running
15405 clock and (if in clock mode) start the other one.
15406 We redisplay both clocks in case we're in ICS mode, because
15407 ICS gives us an update to both clocks after every move.
15408 Note that this routine is called *after* forwardMostMove
15409 is updated, so the last fractional tick must be subtracted
15410 from the color that is *not* on move now.
15413 SwitchClocks(int newMoveNr)
15415 long lastTickLength;
15417 int flagged = FALSE;
15421 if (StopClockTimer() && appData.clockMode) {
15422 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15423 if (!WhiteOnMove(forwardMostMove)) {
15424 if(blackNPS >= 0) lastTickLength = 0;
15425 blackTimeRemaining -= lastTickLength;
15426 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15427 // if(pvInfoList[forwardMostMove].time == -1)
15428 pvInfoList[forwardMostMove].time = // use GUI time
15429 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15431 if(whiteNPS >= 0) lastTickLength = 0;
15432 whiteTimeRemaining -= lastTickLength;
15433 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15434 // if(pvInfoList[forwardMostMove].time == -1)
15435 pvInfoList[forwardMostMove].time =
15436 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15438 flagged = CheckFlags();
15440 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15441 CheckTimeControl();
15443 if (flagged || !appData.clockMode) return;
15445 switch (gameMode) {
15446 case MachinePlaysBlack:
15447 case MachinePlaysWhite:
15448 case BeginningOfGame:
15449 if (pausing) return;
15453 case PlayFromGameFile:
15461 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15462 if(WhiteOnMove(forwardMostMove))
15463 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15464 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15468 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15469 whiteTimeRemaining : blackTimeRemaining);
15470 StartClockTimer(intendedTickLength);
15474 /* Stop both clocks */
15478 long lastTickLength;
15481 if (!StopClockTimer()) return;
15482 if (!appData.clockMode) return;
15486 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15487 if (WhiteOnMove(forwardMostMove)) {
15488 if(whiteNPS >= 0) lastTickLength = 0;
15489 whiteTimeRemaining -= lastTickLength;
15490 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15492 if(blackNPS >= 0) lastTickLength = 0;
15493 blackTimeRemaining -= lastTickLength;
15494 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15499 /* Start clock of player on move. Time may have been reset, so
15500 if clock is already running, stop and restart it. */
15504 (void) StopClockTimer(); /* in case it was running already */
15505 DisplayBothClocks();
15506 if (CheckFlags()) return;
15508 if (!appData.clockMode) return;
15509 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15511 GetTimeMark(&tickStartTM);
15512 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15513 whiteTimeRemaining : blackTimeRemaining);
15515 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15516 whiteNPS = blackNPS = -1;
15517 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15518 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15519 whiteNPS = first.nps;
15520 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15521 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15522 blackNPS = first.nps;
15523 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15524 whiteNPS = second.nps;
15525 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15526 blackNPS = second.nps;
15527 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15529 StartClockTimer(intendedTickLength);
15536 long second, minute, hour, day;
15538 static char buf[32];
15540 if (ms > 0 && ms <= 9900) {
15541 /* convert milliseconds to tenths, rounding up */
15542 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15544 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15548 /* convert milliseconds to seconds, rounding up */
15549 /* use floating point to avoid strangeness of integer division
15550 with negative dividends on many machines */
15551 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15558 day = second / (60 * 60 * 24);
15559 second = second % (60 * 60 * 24);
15560 hour = second / (60 * 60);
15561 second = second % (60 * 60);
15562 minute = second / 60;
15563 second = second % 60;
15566 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15567 sign, day, hour, minute, second);
15569 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15571 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15578 * This is necessary because some C libraries aren't ANSI C compliant yet.
15581 StrStr(string, match)
15582 char *string, *match;
15586 length = strlen(match);
15588 for (i = strlen(string) - length; i >= 0; i--, string++)
15589 if (!strncmp(match, string, length))
15596 StrCaseStr(string, match)
15597 char *string, *match;
15601 length = strlen(match);
15603 for (i = strlen(string) - length; i >= 0; i--, string++) {
15604 for (j = 0; j < length; j++) {
15605 if (ToLower(match[j]) != ToLower(string[j]))
15608 if (j == length) return string;
15622 c1 = ToLower(*s1++);
15623 c2 = ToLower(*s2++);
15624 if (c1 > c2) return 1;
15625 if (c1 < c2) return -1;
15626 if (c1 == NULLCHAR) return 0;
15635 return isupper(c) ? tolower(c) : c;
15643 return islower(c) ? toupper(c) : c;
15645 #endif /* !_amigados */
15653 if ((ret = (char *) malloc(strlen(s) + 1)))
15655 safeStrCpy(ret, s, strlen(s)+1);
15661 StrSavePtr(s, savePtr)
15662 char *s, **savePtr;
15667 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15668 safeStrCpy(*savePtr, s, strlen(s)+1);
15680 clock = time((time_t *)NULL);
15681 tm = localtime(&clock);
15682 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15683 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15684 return StrSave(buf);
15689 PositionToFEN(move, overrideCastling)
15691 char *overrideCastling;
15693 int i, j, fromX, fromY, toX, toY;
15700 whiteToPlay = (gameMode == EditPosition) ?
15701 !blackPlaysFirst : (move % 2 == 0);
15704 /* Piece placement data */
15705 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15707 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15708 if (boards[move][i][j] == EmptySquare) {
15710 } else { ChessSquare piece = boards[move][i][j];
15711 if (emptycount > 0) {
15712 if(emptycount<10) /* [HGM] can be >= 10 */
15713 *p++ = '0' + emptycount;
15714 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15717 if(PieceToChar(piece) == '+') {
15718 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15720 piece = (ChessSquare)(DEMOTED piece);
15722 *p++ = PieceToChar(piece);
15724 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15725 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15730 if (emptycount > 0) {
15731 if(emptycount<10) /* [HGM] can be >= 10 */
15732 *p++ = '0' + emptycount;
15733 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15740 /* [HGM] print Crazyhouse or Shogi holdings */
15741 if( gameInfo.holdingsWidth ) {
15742 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15744 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15745 piece = boards[move][i][BOARD_WIDTH-1];
15746 if( piece != EmptySquare )
15747 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15748 *p++ = PieceToChar(piece);
15750 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15751 piece = boards[move][BOARD_HEIGHT-i-1][0];
15752 if( piece != EmptySquare )
15753 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15754 *p++ = PieceToChar(piece);
15757 if( q == p ) *p++ = '-';
15763 *p++ = whiteToPlay ? 'w' : 'b';
15766 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15767 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15769 if(nrCastlingRights) {
15771 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15772 /* [HGM] write directly from rights */
15773 if(boards[move][CASTLING][2] != NoRights &&
15774 boards[move][CASTLING][0] != NoRights )
15775 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15776 if(boards[move][CASTLING][2] != NoRights &&
15777 boards[move][CASTLING][1] != NoRights )
15778 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15779 if(boards[move][CASTLING][5] != NoRights &&
15780 boards[move][CASTLING][3] != NoRights )
15781 *p++ = boards[move][CASTLING][3] + AAA;
15782 if(boards[move][CASTLING][5] != NoRights &&
15783 boards[move][CASTLING][4] != NoRights )
15784 *p++ = boards[move][CASTLING][4] + AAA;
15787 /* [HGM] write true castling rights */
15788 if( nrCastlingRights == 6 ) {
15789 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15790 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15791 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15792 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15793 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15794 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15795 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15796 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15799 if (q == p) *p++ = '-'; /* No castling rights */
15803 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15804 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15805 /* En passant target square */
15806 if (move > backwardMostMove) {
15807 fromX = moveList[move - 1][0] - AAA;
15808 fromY = moveList[move - 1][1] - ONE;
15809 toX = moveList[move - 1][2] - AAA;
15810 toY = moveList[move - 1][3] - ONE;
15811 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15812 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15813 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15815 /* 2-square pawn move just happened */
15817 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15821 } else if(move == backwardMostMove) {
15822 // [HGM] perhaps we should always do it like this, and forget the above?
15823 if((signed char)boards[move][EP_STATUS] >= 0) {
15824 *p++ = boards[move][EP_STATUS] + AAA;
15825 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15836 /* [HGM] find reversible plies */
15837 { int i = 0, j=move;
15839 if (appData.debugMode) { int k;
15840 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15841 for(k=backwardMostMove; k<=forwardMostMove; k++)
15842 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15846 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15847 if( j == backwardMostMove ) i += initialRulePlies;
15848 sprintf(p, "%d ", i);
15849 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15851 /* Fullmove number */
15852 sprintf(p, "%d", (move / 2) + 1);
15854 return StrSave(buf);
15858 ParseFEN(board, blackPlaysFirst, fen)
15860 int *blackPlaysFirst;
15870 /* [HGM] by default clear Crazyhouse holdings, if present */
15871 if(gameInfo.holdingsWidth) {
15872 for(i=0; i<BOARD_HEIGHT; i++) {
15873 board[i][0] = EmptySquare; /* black holdings */
15874 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15875 board[i][1] = (ChessSquare) 0; /* black counts */
15876 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15880 /* Piece placement data */
15881 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15884 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15885 if (*p == '/') p++;
15886 emptycount = gameInfo.boardWidth - j;
15887 while (emptycount--)
15888 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15890 #if(BOARD_FILES >= 10)
15891 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15892 p++; emptycount=10;
15893 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15894 while (emptycount--)
15895 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15897 } else if (isdigit(*p)) {
15898 emptycount = *p++ - '0';
15899 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15900 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15901 while (emptycount--)
15902 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15903 } else if (*p == '+' || isalpha(*p)) {
15904 if (j >= gameInfo.boardWidth) return FALSE;
15906 piece = CharToPiece(*++p);
15907 if(piece == EmptySquare) return FALSE; /* unknown piece */
15908 piece = (ChessSquare) (PROMOTED piece ); p++;
15909 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15910 } else piece = CharToPiece(*p++);
15912 if(piece==EmptySquare) return FALSE; /* unknown piece */
15913 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15914 piece = (ChessSquare) (PROMOTED piece);
15915 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15918 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15924 while (*p == '/' || *p == ' ') p++;
15926 /* [HGM] look for Crazyhouse holdings here */
15927 while(*p==' ') p++;
15928 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15930 if(*p == '-' ) p++; /* empty holdings */ else {
15931 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15932 /* if we would allow FEN reading to set board size, we would */
15933 /* have to add holdings and shift the board read so far here */
15934 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15936 if((int) piece >= (int) BlackPawn ) {
15937 i = (int)piece - (int)BlackPawn;
15938 i = PieceToNumber((ChessSquare)i);
15939 if( i >= gameInfo.holdingsSize ) return FALSE;
15940 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15941 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15943 i = (int)piece - (int)WhitePawn;
15944 i = PieceToNumber((ChessSquare)i);
15945 if( i >= gameInfo.holdingsSize ) return FALSE;
15946 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15947 board[i][BOARD_WIDTH-2]++; /* black holdings */
15954 while(*p == ' ') p++;
15958 if(appData.colorNickNames) {
15959 if( c == appData.colorNickNames[0] ) c = 'w'; else
15960 if( c == appData.colorNickNames[1] ) c = 'b';
15964 *blackPlaysFirst = FALSE;
15967 *blackPlaysFirst = TRUE;
15973 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15974 /* return the extra info in global variiables */
15976 /* set defaults in case FEN is incomplete */
15977 board[EP_STATUS] = EP_UNKNOWN;
15978 for(i=0; i<nrCastlingRights; i++ ) {
15979 board[CASTLING][i] =
15980 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15981 } /* assume possible unless obviously impossible */
15982 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15983 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15984 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15985 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15986 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15987 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15988 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15989 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15992 while(*p==' ') p++;
15993 if(nrCastlingRights) {
15994 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15995 /* castling indicator present, so default becomes no castlings */
15996 for(i=0; i<nrCastlingRights; i++ ) {
15997 board[CASTLING][i] = NoRights;
16000 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16001 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16002 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16003 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16004 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16006 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16007 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16008 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16010 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16011 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16012 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16013 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16014 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16015 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16018 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16019 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16020 board[CASTLING][2] = whiteKingFile;
16023 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16024 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16025 board[CASTLING][2] = whiteKingFile;
16028 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16029 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16030 board[CASTLING][5] = blackKingFile;
16033 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16034 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16035 board[CASTLING][5] = blackKingFile;
16038 default: /* FRC castlings */
16039 if(c >= 'a') { /* black rights */
16040 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16041 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16042 if(i == BOARD_RGHT) break;
16043 board[CASTLING][5] = i;
16045 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16046 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16048 board[CASTLING][3] = c;
16050 board[CASTLING][4] = c;
16051 } else { /* white rights */
16052 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16053 if(board[0][i] == WhiteKing) break;
16054 if(i == BOARD_RGHT) break;
16055 board[CASTLING][2] = i;
16056 c -= AAA - 'a' + 'A';
16057 if(board[0][c] >= WhiteKing) break;
16059 board[CASTLING][0] = c;
16061 board[CASTLING][1] = c;
16065 for(i=0; i<nrCastlingRights; i++)
16066 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16067 if (appData.debugMode) {
16068 fprintf(debugFP, "FEN castling rights:");
16069 for(i=0; i<nrCastlingRights; i++)
16070 fprintf(debugFP, " %d", board[CASTLING][i]);
16071 fprintf(debugFP, "\n");
16074 while(*p==' ') p++;
16077 /* read e.p. field in games that know e.p. capture */
16078 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16079 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16081 p++; board[EP_STATUS] = EP_NONE;
16083 char c = *p++ - AAA;
16085 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16086 if(*p >= '0' && *p <='9') p++;
16087 board[EP_STATUS] = c;
16092 if(sscanf(p, "%d", &i) == 1) {
16093 FENrulePlies = i; /* 50-move ply counter */
16094 /* (The move number is still ignored) */
16101 EditPositionPasteFEN(char *fen)
16104 Board initial_position;
16106 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16107 DisplayError(_("Bad FEN position in clipboard"), 0);
16110 int savedBlackPlaysFirst = blackPlaysFirst;
16111 EditPositionEvent();
16112 blackPlaysFirst = savedBlackPlaysFirst;
16113 CopyBoard(boards[0], initial_position);
16114 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16115 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16116 DisplayBothClocks();
16117 DrawPosition(FALSE, boards[currentMove]);
16122 static char cseq[12] = "\\ ";
16124 Boolean set_cont_sequence(char *new_seq)
16129 // handle bad attempts to set the sequence
16131 return 0; // acceptable error - no debug
16133 len = strlen(new_seq);
16134 ret = (len > 0) && (len < sizeof(cseq));
16136 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16137 else if (appData.debugMode)
16138 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16143 reformat a source message so words don't cross the width boundary. internal
16144 newlines are not removed. returns the wrapped size (no null character unless
16145 included in source message). If dest is NULL, only calculate the size required
16146 for the dest buffer. lp argument indicats line position upon entry, and it's
16147 passed back upon exit.
16149 int wrap(char *dest, char *src, int count, int width, int *lp)
16151 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16153 cseq_len = strlen(cseq);
16154 old_line = line = *lp;
16155 ansi = len = clen = 0;
16157 for (i=0; i < count; i++)
16159 if (src[i] == '\033')
16162 // if we hit the width, back up
16163 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16165 // store i & len in case the word is too long
16166 old_i = i, old_len = len;
16168 // find the end of the last word
16169 while (i && src[i] != ' ' && src[i] != '\n')
16175 // word too long? restore i & len before splitting it
16176 if ((old_i-i+clen) >= width)
16183 if (i && src[i-1] == ' ')
16186 if (src[i] != ' ' && src[i] != '\n')
16193 // now append the newline and continuation sequence
16198 strncpy(dest+len, cseq, cseq_len);
16206 dest[len] = src[i];
16210 if (src[i] == '\n')
16215 if (dest && appData.debugMode)
16217 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16218 count, width, line, len, *lp);
16219 show_bytes(debugFP, src, count);
16220 fprintf(debugFP, "\ndest: ");
16221 show_bytes(debugFP, dest, len);
16222 fprintf(debugFP, "\n");
16224 *lp = dest ? line : old_line;
16229 // [HGM] vari: routines for shelving variations
16232 PushInner(int firstMove, int lastMove)
16234 int i, j, nrMoves = lastMove - firstMove;
16236 // push current tail of game on stack
16237 savedResult[storedGames] = gameInfo.result;
16238 savedDetails[storedGames] = gameInfo.resultDetails;
16239 gameInfo.resultDetails = NULL;
16240 savedFirst[storedGames] = firstMove;
16241 savedLast [storedGames] = lastMove;
16242 savedFramePtr[storedGames] = framePtr;
16243 framePtr -= nrMoves; // reserve space for the boards
16244 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16245 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16246 for(j=0; j<MOVE_LEN; j++)
16247 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16248 for(j=0; j<2*MOVE_LEN; j++)
16249 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16250 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16251 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16252 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16253 pvInfoList[firstMove+i-1].depth = 0;
16254 commentList[framePtr+i] = commentList[firstMove+i];
16255 commentList[firstMove+i] = NULL;
16259 forwardMostMove = firstMove; // truncate game so we can start variation
16263 PushTail(int firstMove, int lastMove)
16265 if(appData.icsActive) { // only in local mode
16266 forwardMostMove = currentMove; // mimic old ICS behavior
16269 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16271 PushInner(firstMove, lastMove);
16272 if(storedGames == 1) GreyRevert(FALSE);
16276 PopInner(Boolean annotate)
16279 char buf[8000], moveBuf[20];
16282 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16283 nrMoves = savedLast[storedGames] - currentMove;
16286 if(!WhiteOnMove(currentMove))
16287 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16288 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16289 for(i=currentMove; i<forwardMostMove; i++) {
16291 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16292 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16293 strcat(buf, moveBuf);
16294 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16295 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16299 for(i=1; i<=nrMoves; i++) { // copy last variation back
16300 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16301 for(j=0; j<MOVE_LEN; j++)
16302 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16303 for(j=0; j<2*MOVE_LEN; j++)
16304 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16305 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16306 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16307 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16308 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16309 commentList[currentMove+i] = commentList[framePtr+i];
16310 commentList[framePtr+i] = NULL;
16312 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16313 framePtr = savedFramePtr[storedGames];
16314 gameInfo.result = savedResult[storedGames];
16315 if(gameInfo.resultDetails != NULL) {
16316 free(gameInfo.resultDetails);
16318 gameInfo.resultDetails = savedDetails[storedGames];
16319 forwardMostMove = currentMove + nrMoves;
16323 PopTail(Boolean annotate)
16325 if(appData.icsActive) return FALSE; // only in local mode
16326 if(!storedGames) return FALSE; // sanity
16327 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16329 PopInner(annotate);
16331 if(storedGames == 0) GreyRevert(TRUE);
16337 { // remove all shelved variations
16339 for(i=0; i<storedGames; i++) {
16340 if(savedDetails[i])
16341 free(savedDetails[i]);
16342 savedDetails[i] = NULL;
16344 for(i=framePtr; i<MAX_MOVES; i++) {
16345 if(commentList[i]) free(commentList[i]);
16346 commentList[i] = NULL;
16348 framePtr = MAX_MOVES-1;
16353 LoadVariation(int index, char *text)
16354 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16355 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16356 int level = 0, move;
16358 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16359 // first find outermost bracketing variation
16360 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16361 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16362 if(*p == '{') wait = '}'; else
16363 if(*p == '[') wait = ']'; else
16364 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16365 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16367 if(*p == wait) wait = NULLCHAR; // closing ]} found
16370 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16371 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16372 end[1] = NULLCHAR; // clip off comment beyond variation
16373 ToNrEvent(currentMove-1);
16374 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16375 // kludge: use ParsePV() to append variation to game
16376 move = currentMove;
16377 ParsePV(start, TRUE, TRUE);
16378 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16379 ClearPremoveHighlights();
16381 ToNrEvent(currentMove+1);