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 if(mode == 2) 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 MakeName(char *template)
9561 static char buf[MSG_SIZ];
9565 clock = time((time_t *)NULL);
9566 tm = localtime(&clock);
9568 while(*p++ = *template++) if(p[-1] == '%') {
9569 switch(*template++) {
9570 case 0: *p = 0; return buf;
9571 case 'Y': i = tm->tm_year+1900; break;
9572 case 'y': i = tm->tm_year-100; break;
9573 case 'M': i = tm->tm_mon+1; break;
9574 case 'd': i = tm->tm_mday; break;
9575 case 'h': i = tm->tm_hour; break;
9576 case 'm': i = tm->tm_min; break;
9577 case 's': i = tm->tm_sec; break;
9580 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9586 CountPlayers(char *p)
9589 while(p = strchr(p, '\n')) p++, n++; // count participants
9594 WriteTourneyFile(char *results)
9595 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9596 FILE *f = fopen(appData.tourneyFile, "w");
9597 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9598 // create a file with tournament description
9599 fprintf(f, "-participants {%s}\n", appData.participants);
9600 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9601 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9602 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9603 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9604 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9605 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9606 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9607 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9608 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9609 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9610 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9612 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9614 fprintf(f, "-mps %d\n", appData.movesPerSession);
9615 fprintf(f, "-tc %s\n", appData.timeControl);
9616 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9618 fprintf(f, "-results \"%s\"\n", results);
9624 CreateTourney(char *name)
9627 if(name[0] == NULLCHAR) {
9628 if(appData.participants[0])
9629 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9632 f = fopen(name, "r");
9633 if(f) { // file exists
9634 ASSIGN(appData.tourneyFile, name);
9635 ParseArgsFromFile(f); // parse it
9637 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9638 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9639 DisplayError(_("Not enough participants"), 0);
9642 ASSIGN(appData.tourneyFile, name);
9643 if((f = WriteTourneyFile("")) == NULL) return 0;
9646 appData.noChessProgram = FALSE;
9647 appData.clockMode = TRUE;
9652 #define MAXENGINES 1000
9653 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9655 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9657 char buf[MSG_SIZ], *p, *q;
9661 while(*p && *p != '\n') *q++ = *p++;
9663 if(engineList[i]) free(engineList[i]);
9664 engineList[i] = strdup(buf);
9666 TidyProgramName(engineList[i], "localhost", buf);
9667 if(engineMnemonic[i]) free(engineMnemonic[i]);
9668 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9670 sscanf(q + 8, "%s", buf + strlen(buf));
9673 engineMnemonic[i] = strdup(buf);
9675 if(i > MAXENGINES - 2) break;
9677 engineList[i] = NULL;
9680 // following implemented as macro to avoid type limitations
9681 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9683 void SwapEngines(int n)
9684 { // swap settings for first engine and other engine (so far only some selected options)
9689 SWAP(chessProgram, p)
9691 SWAP(hasOwnBookUCI, h)
9692 SWAP(protocolVersion, h)
9694 SWAP(scoreIsAbsolute, h)
9701 SetPlayer(int player)
9702 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9704 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9705 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9706 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9707 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9709 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9710 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9711 ParseArgsFromString(buf);
9717 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9718 { // determine players from game number
9719 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9721 if(appData.tourneyType == 0) {
9722 roundsPerCycle = (nPlayers - 1) | 1;
9723 pairingsPerRound = nPlayers / 2;
9724 } else if(appData.tourneyType > 0) {
9725 roundsPerCycle = nPlayers - appData.tourneyType;
9726 pairingsPerRound = appData.tourneyType;
9728 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9729 gamesPerCycle = gamesPerRound * roundsPerCycle;
9730 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9731 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9732 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9733 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9734 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9735 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9737 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9738 if(appData.roundSync) *syncInterval = gamesPerRound;
9740 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9742 if(appData.tourneyType == 0) {
9743 if(curPairing == (nPlayers-1)/2 ) {
9744 *whitePlayer = curRound;
9745 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9747 *whitePlayer = curRound - pairingsPerRound + curPairing;
9748 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9749 *blackPlayer = curRound + pairingsPerRound - curPairing;
9750 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9752 } else if(appData.tourneyType > 0) {
9753 *whitePlayer = curPairing;
9754 *blackPlayer = curRound + appData.tourneyType;
9757 // take care of white/black alternation per round.
9758 // For cycles and games this is already taken care of by default, derived from matchGame!
9759 return curRound & 1;
9763 NextTourneyGame(int nr, int *swapColors)
9764 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9766 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9768 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9769 tf = fopen(appData.tourneyFile, "r");
9770 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9771 ParseArgsFromFile(tf); fclose(tf);
9772 InitTimeControls(); // TC might be altered from tourney file
9774 nPlayers = CountPlayers(appData.participants); // count participants
9775 if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9776 if(nr>=0 && !pairingReceived) {
9778 if(pairing.pr == NoProc) StartChessProgram(&pairing);
9779 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9780 SendToProgram(buf, &pairing);
9781 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9782 SendToProgram(buf, &pairing);
9783 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9785 pairingReceived = 0; // ... so we continue here
9786 syncInterval = nPlayers/2; *swapColors = 0;
9787 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9788 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9789 matchGame = 1; roundNr = nr / syncInterval + 1;
9791 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9794 p = q = appData.results;
9795 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9796 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9797 DisplayMessage(_("Waiting for other game(s)"),"");
9798 waitingForGame = TRUE;
9799 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9802 waitingForGame = FALSE;
9805 if(first.pr != NoProc) return 1; // engines already loaded
9807 // redefine engines, engine dir, etc.
9808 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9809 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9811 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9812 SwapEngines(1); // and make that valid for second engine by swapping
9813 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9814 InitEngine(&second, 1);
9815 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9821 { // performs game initialization that does not invoke engines, and then tries to start the game
9822 int firstWhite, swapColors = 0;
9823 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9824 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9825 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9826 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9827 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9828 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9829 Reset(FALSE, first.pr != NoProc);
9830 appData.noChessProgram = FALSE;
9831 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9835 void UserAdjudicationEvent( int result )
9837 ChessMove gameResult = GameIsDrawn;
9840 gameResult = WhiteWins;
9842 else if( result < 0 ) {
9843 gameResult = BlackWins;
9846 if( gameMode == TwoMachinesPlay ) {
9847 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9852 // [HGM] save: calculate checksum of game to make games easily identifiable
9853 int StringCheckSum(char *s)
9856 if(s==NULL) return 0;
9857 while(*s) i = i*259 + *s++;
9864 for(i=backwardMostMove; i<forwardMostMove; i++) {
9865 sum += pvInfoList[i].depth;
9866 sum += StringCheckSum(parseList[i]);
9867 sum += StringCheckSum(commentList[i]);
9870 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9871 return sum + StringCheckSum(commentList[i]);
9872 } // end of save patch
9875 GameEnds(result, resultDetails, whosays)
9877 char *resultDetails;
9880 GameMode nextGameMode;
9882 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9884 if(endingGame) return; /* [HGM] crash: forbid recursion */
9886 if(twoBoards) { // [HGM] dual: switch back to one board
9887 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9888 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9890 if (appData.debugMode) {
9891 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9892 result, resultDetails ? resultDetails : "(null)", whosays);
9895 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9897 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9898 /* If we are playing on ICS, the server decides when the
9899 game is over, but the engine can offer to draw, claim
9903 if (appData.zippyPlay && first.initDone) {
9904 if (result == GameIsDrawn) {
9905 /* In case draw still needs to be claimed */
9906 SendToICS(ics_prefix);
9907 SendToICS("draw\n");
9908 } else if (StrCaseStr(resultDetails, "resign")) {
9909 SendToICS(ics_prefix);
9910 SendToICS("resign\n");
9914 endingGame = 0; /* [HGM] crash */
9918 /* If we're loading the game from a file, stop */
9919 if (whosays == GE_FILE) {
9920 (void) StopLoadGameTimer();
9924 /* Cancel draw offers */
9925 first.offeredDraw = second.offeredDraw = 0;
9927 /* If this is an ICS game, only ICS can really say it's done;
9928 if not, anyone can. */
9929 isIcsGame = (gameMode == IcsPlayingWhite ||
9930 gameMode == IcsPlayingBlack ||
9931 gameMode == IcsObserving ||
9932 gameMode == IcsExamining);
9934 if (!isIcsGame || whosays == GE_ICS) {
9935 /* OK -- not an ICS game, or ICS said it was done */
9937 if (!isIcsGame && !appData.noChessProgram)
9938 SetUserThinkingEnables();
9940 /* [HGM] if a machine claims the game end we verify this claim */
9941 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9942 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9944 ChessMove trueResult = (ChessMove) -1;
9946 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9947 first.twoMachinesColor[0] :
9948 second.twoMachinesColor[0] ;
9950 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9951 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9952 /* [HGM] verify: engine mate claims accepted if they were flagged */
9953 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9955 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9956 /* [HGM] verify: engine mate claims accepted if they were flagged */
9957 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9959 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9960 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9963 // now verify win claims, but not in drop games, as we don't understand those yet
9964 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9965 || gameInfo.variant == VariantGreat) &&
9966 (result == WhiteWins && claimer == 'w' ||
9967 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9968 if (appData.debugMode) {
9969 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9970 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9972 if(result != trueResult) {
9973 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9974 result = claimer == 'w' ? BlackWins : WhiteWins;
9975 resultDetails = buf;
9978 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9979 && (forwardMostMove <= backwardMostMove ||
9980 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9981 (claimer=='b')==(forwardMostMove&1))
9983 /* [HGM] verify: draws that were not flagged are false claims */
9984 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9985 result = claimer == 'w' ? BlackWins : WhiteWins;
9986 resultDetails = buf;
9988 /* (Claiming a loss is accepted no questions asked!) */
9990 /* [HGM] bare: don't allow bare King to win */
9991 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9992 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9993 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9994 && result != GameIsDrawn)
9995 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9996 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9997 int p = (signed char)boards[forwardMostMove][i][j] - color;
9998 if(p >= 0 && p <= (int)WhiteKing) k++;
10000 if (appData.debugMode) {
10001 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10002 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10005 result = GameIsDrawn;
10006 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10007 resultDetails = buf;
10013 if(serverMoves != NULL && !loadFlag) { char c = '=';
10014 if(result==WhiteWins) c = '+';
10015 if(result==BlackWins) c = '-';
10016 if(resultDetails != NULL)
10017 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10019 if (resultDetails != NULL) {
10020 gameInfo.result = result;
10021 gameInfo.resultDetails = StrSave(resultDetails);
10023 /* display last move only if game was not loaded from file */
10024 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10025 DisplayMove(currentMove - 1);
10027 if (forwardMostMove != 0) {
10028 if (gameMode != PlayFromGameFile && gameMode != EditGame
10029 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10031 if (*appData.saveGameFile != NULLCHAR) {
10032 SaveGameToFile(appData.saveGameFile, TRUE);
10033 } else if (appData.autoSaveGames) {
10036 if (*appData.savePositionFile != NULLCHAR) {
10037 SavePositionToFile(appData.savePositionFile);
10042 /* Tell program how game ended in case it is learning */
10043 /* [HGM] Moved this to after saving the PGN, just in case */
10044 /* engine died and we got here through time loss. In that */
10045 /* case we will get a fatal error writing the pipe, which */
10046 /* would otherwise lose us the PGN. */
10047 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10048 /* output during GameEnds should never be fatal anymore */
10049 if (gameMode == MachinePlaysWhite ||
10050 gameMode == MachinePlaysBlack ||
10051 gameMode == TwoMachinesPlay ||
10052 gameMode == IcsPlayingWhite ||
10053 gameMode == IcsPlayingBlack ||
10054 gameMode == BeginningOfGame) {
10056 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10058 if (first.pr != NoProc) {
10059 SendToProgram(buf, &first);
10061 if (second.pr != NoProc &&
10062 gameMode == TwoMachinesPlay) {
10063 SendToProgram(buf, &second);
10068 if (appData.icsActive) {
10069 if (appData.quietPlay &&
10070 (gameMode == IcsPlayingWhite ||
10071 gameMode == IcsPlayingBlack)) {
10072 SendToICS(ics_prefix);
10073 SendToICS("set shout 1\n");
10075 nextGameMode = IcsIdle;
10076 ics_user_moved = FALSE;
10077 /* clean up premove. It's ugly when the game has ended and the
10078 * premove highlights are still on the board.
10081 gotPremove = FALSE;
10082 ClearPremoveHighlights();
10083 DrawPosition(FALSE, boards[currentMove]);
10085 if (whosays == GE_ICS) {
10088 if (gameMode == IcsPlayingWhite)
10090 else if(gameMode == IcsPlayingBlack)
10091 PlayIcsLossSound();
10094 if (gameMode == IcsPlayingBlack)
10096 else if(gameMode == IcsPlayingWhite)
10097 PlayIcsLossSound();
10100 PlayIcsDrawSound();
10103 PlayIcsUnfinishedSound();
10106 } else if (gameMode == EditGame ||
10107 gameMode == PlayFromGameFile ||
10108 gameMode == AnalyzeMode ||
10109 gameMode == AnalyzeFile) {
10110 nextGameMode = gameMode;
10112 nextGameMode = EndOfGame;
10117 nextGameMode = gameMode;
10120 if (appData.noChessProgram) {
10121 gameMode = nextGameMode;
10123 endingGame = 0; /* [HGM] crash */
10128 /* Put first chess program into idle state */
10129 if (first.pr != NoProc &&
10130 (gameMode == MachinePlaysWhite ||
10131 gameMode == MachinePlaysBlack ||
10132 gameMode == TwoMachinesPlay ||
10133 gameMode == IcsPlayingWhite ||
10134 gameMode == IcsPlayingBlack ||
10135 gameMode == BeginningOfGame)) {
10136 SendToProgram("force\n", &first);
10137 if (first.usePing) {
10139 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10140 SendToProgram(buf, &first);
10143 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10144 /* Kill off first chess program */
10145 if (first.isr != NULL)
10146 RemoveInputSource(first.isr);
10149 if (first.pr != NoProc) {
10151 DoSleep( appData.delayBeforeQuit );
10152 SendToProgram("quit\n", &first);
10153 DoSleep( appData.delayAfterQuit );
10154 DestroyChildProcess(first.pr, first.useSigterm);
10158 if (second.reuse) {
10159 /* Put second chess program into idle state */
10160 if (second.pr != NoProc &&
10161 gameMode == TwoMachinesPlay) {
10162 SendToProgram("force\n", &second);
10163 if (second.usePing) {
10165 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10166 SendToProgram(buf, &second);
10169 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10170 /* Kill off second chess program */
10171 if (second.isr != NULL)
10172 RemoveInputSource(second.isr);
10175 if (second.pr != NoProc) {
10176 DoSleep( appData.delayBeforeQuit );
10177 SendToProgram("quit\n", &second);
10178 DoSleep( appData.delayAfterQuit );
10179 DestroyChildProcess(second.pr, second.useSigterm);
10181 second.pr = NoProc;
10184 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10185 char resChar = '=';
10189 if (first.twoMachinesColor[0] == 'w') {
10192 second.matchWins++;
10197 if (first.twoMachinesColor[0] == 'b') {
10200 second.matchWins++;
10203 case GameUnfinished:
10209 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10210 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10211 ReserveGame(nextGame, resChar); // sets nextGame
10212 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10213 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10215 if (nextGame <= appData.matchGames && !abortMatch) {
10216 gameMode = nextGameMode;
10217 matchGame = nextGame; // this will be overruled in tourney mode!
10218 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10219 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10220 endingGame = 0; /* [HGM] crash */
10223 gameMode = nextGameMode;
10224 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10225 first.tidy, second.tidy,
10226 first.matchWins, second.matchWins,
10227 appData.matchGames - (first.matchWins + second.matchWins));
10228 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10229 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10230 first.twoMachinesColor = "black\n";
10231 second.twoMachinesColor = "white\n";
10233 first.twoMachinesColor = "white\n";
10234 second.twoMachinesColor = "black\n";
10238 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10239 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10241 gameMode = nextGameMode;
10243 endingGame = 0; /* [HGM] crash */
10244 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10245 if(matchMode == TRUE) { // match through command line: exit with or without popup
10247 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10249 } else DisplayFatalError(buf, 0, 0);
10250 } else { // match through menu; just stop, with or without popup
10251 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10253 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10254 } else DisplayNote(buf);
10256 if(ranking) free(ranking);
10260 /* Assumes program was just initialized (initString sent).
10261 Leaves program in force mode. */
10263 FeedMovesToProgram(cps, upto)
10264 ChessProgramState *cps;
10269 if (appData.debugMode)
10270 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10271 startedFromSetupPosition ? "position and " : "",
10272 backwardMostMove, upto, cps->which);
10273 if(currentlyInitializedVariant != gameInfo.variant) {
10275 // [HGM] variantswitch: make engine aware of new variant
10276 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10277 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10278 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10279 SendToProgram(buf, cps);
10280 currentlyInitializedVariant = gameInfo.variant;
10282 SendToProgram("force\n", cps);
10283 if (startedFromSetupPosition) {
10284 SendBoard(cps, backwardMostMove);
10285 if (appData.debugMode) {
10286 fprintf(debugFP, "feedMoves\n");
10289 for (i = backwardMostMove; i < upto; i++) {
10290 SendMoveToProgram(i, cps);
10296 ResurrectChessProgram()
10298 /* The chess program may have exited.
10299 If so, restart it and feed it all the moves made so far. */
10300 static int doInit = 0;
10302 if (appData.noChessProgram) return 1;
10304 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10305 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10306 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10307 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10309 if (first.pr != NoProc) return 1;
10310 StartChessProgram(&first);
10312 InitChessProgram(&first, FALSE);
10313 FeedMovesToProgram(&first, currentMove);
10315 if (!first.sendTime) {
10316 /* can't tell gnuchess what its clock should read,
10317 so we bow to its notion. */
10319 timeRemaining[0][currentMove] = whiteTimeRemaining;
10320 timeRemaining[1][currentMove] = blackTimeRemaining;
10323 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10324 appData.icsEngineAnalyze) && first.analysisSupport) {
10325 SendToProgram("analyze\n", &first);
10326 first.analyzing = TRUE;
10332 * Button procedures
10335 Reset(redraw, init)
10340 if (appData.debugMode) {
10341 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10342 redraw, init, gameMode);
10344 CleanupTail(); // [HGM] vari: delete any stored variations
10345 pausing = pauseExamInvalid = FALSE;
10346 startedFromSetupPosition = blackPlaysFirst = FALSE;
10348 whiteFlag = blackFlag = FALSE;
10349 userOfferedDraw = FALSE;
10350 hintRequested = bookRequested = FALSE;
10351 first.maybeThinking = FALSE;
10352 second.maybeThinking = FALSE;
10353 first.bookSuspend = FALSE; // [HGM] book
10354 second.bookSuspend = FALSE;
10355 thinkOutput[0] = NULLCHAR;
10356 lastHint[0] = NULLCHAR;
10357 ClearGameInfo(&gameInfo);
10358 gameInfo.variant = StringToVariant(appData.variant);
10359 ics_user_moved = ics_clock_paused = FALSE;
10360 ics_getting_history = H_FALSE;
10362 white_holding[0] = black_holding[0] = NULLCHAR;
10363 ClearProgramStats();
10364 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10368 flipView = appData.flipView;
10369 ClearPremoveHighlights();
10370 gotPremove = FALSE;
10371 alarmSounded = FALSE;
10373 GameEnds(EndOfFile, NULL, GE_PLAYER);
10374 if(appData.serverMovesName != NULL) {
10375 /* [HGM] prepare to make moves file for broadcasting */
10376 clock_t t = clock();
10377 if(serverMoves != NULL) fclose(serverMoves);
10378 serverMoves = fopen(appData.serverMovesName, "r");
10379 if(serverMoves != NULL) {
10380 fclose(serverMoves);
10381 /* delay 15 sec before overwriting, so all clients can see end */
10382 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10384 serverMoves = fopen(appData.serverMovesName, "w");
10388 gameMode = BeginningOfGame;
10390 if(appData.icsActive) gameInfo.variant = VariantNormal;
10391 currentMove = forwardMostMove = backwardMostMove = 0;
10392 InitPosition(redraw);
10393 for (i = 0; i < MAX_MOVES; i++) {
10394 if (commentList[i] != NULL) {
10395 free(commentList[i]);
10396 commentList[i] = NULL;
10400 timeRemaining[0][0] = whiteTimeRemaining;
10401 timeRemaining[1][0] = blackTimeRemaining;
10403 if (first.pr == NULL) {
10404 StartChessProgram(&first);
10407 InitChessProgram(&first, startedFromSetupPosition);
10410 DisplayMessage("", "");
10411 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10412 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10419 if (!AutoPlayOneMove())
10421 if (matchMode || appData.timeDelay == 0)
10423 if (appData.timeDelay < 0)
10425 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10434 int fromX, fromY, toX, toY;
10436 if (appData.debugMode) {
10437 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10440 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10443 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10444 pvInfoList[currentMove].depth = programStats.depth;
10445 pvInfoList[currentMove].score = programStats.score;
10446 pvInfoList[currentMove].time = 0;
10447 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10450 if (currentMove >= forwardMostMove) {
10451 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10452 gameMode = EditGame;
10455 /* [AS] Clear current move marker at the end of a game */
10456 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10461 toX = moveList[currentMove][2] - AAA;
10462 toY = moveList[currentMove][3] - ONE;
10464 if (moveList[currentMove][1] == '@') {
10465 if (appData.highlightLastMove) {
10466 SetHighlights(-1, -1, toX, toY);
10469 fromX = moveList[currentMove][0] - AAA;
10470 fromY = moveList[currentMove][1] - ONE;
10472 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10474 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10476 if (appData.highlightLastMove) {
10477 SetHighlights(fromX, fromY, toX, toY);
10480 DisplayMove(currentMove);
10481 SendMoveToProgram(currentMove++, &first);
10482 DisplayBothClocks();
10483 DrawPosition(FALSE, boards[currentMove]);
10484 // [HGM] PV info: always display, routine tests if empty
10485 DisplayComment(currentMove - 1, commentList[currentMove]);
10491 LoadGameOneMove(readAhead)
10492 ChessMove readAhead;
10494 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10495 char promoChar = NULLCHAR;
10496 ChessMove moveType;
10497 char move[MSG_SIZ];
10500 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10501 gameMode != AnalyzeMode && gameMode != Training) {
10506 yyboardindex = forwardMostMove;
10507 if (readAhead != EndOfFile) {
10508 moveType = readAhead;
10510 if (gameFileFP == NULL)
10512 moveType = (ChessMove) Myylex();
10516 switch (moveType) {
10518 if (appData.debugMode)
10519 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10522 /* append the comment but don't display it */
10523 AppendComment(currentMove, p, FALSE);
10526 case WhiteCapturesEnPassant:
10527 case BlackCapturesEnPassant:
10528 case WhitePromotion:
10529 case BlackPromotion:
10530 case WhiteNonPromotion:
10531 case BlackNonPromotion:
10533 case WhiteKingSideCastle:
10534 case WhiteQueenSideCastle:
10535 case BlackKingSideCastle:
10536 case BlackQueenSideCastle:
10537 case WhiteKingSideCastleWild:
10538 case WhiteQueenSideCastleWild:
10539 case BlackKingSideCastleWild:
10540 case BlackQueenSideCastleWild:
10542 case WhiteHSideCastleFR:
10543 case WhiteASideCastleFR:
10544 case BlackHSideCastleFR:
10545 case BlackASideCastleFR:
10547 if (appData.debugMode)
10548 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10549 fromX = currentMoveString[0] - AAA;
10550 fromY = currentMoveString[1] - ONE;
10551 toX = currentMoveString[2] - AAA;
10552 toY = currentMoveString[3] - ONE;
10553 promoChar = currentMoveString[4];
10558 if (appData.debugMode)
10559 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10560 fromX = moveType == WhiteDrop ?
10561 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10562 (int) CharToPiece(ToLower(currentMoveString[0]));
10564 toX = currentMoveString[2] - AAA;
10565 toY = currentMoveString[3] - ONE;
10571 case GameUnfinished:
10572 if (appData.debugMode)
10573 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10574 p = strchr(yy_text, '{');
10575 if (p == NULL) p = strchr(yy_text, '(');
10578 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10580 q = strchr(p, *p == '{' ? '}' : ')');
10581 if (q != NULL) *q = NULLCHAR;
10584 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10585 GameEnds(moveType, p, GE_FILE);
10587 if (cmailMsgLoaded) {
10589 flipView = WhiteOnMove(currentMove);
10590 if (moveType == GameUnfinished) flipView = !flipView;
10591 if (appData.debugMode)
10592 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10597 if (appData.debugMode)
10598 fprintf(debugFP, "Parser hit end of file\n");
10599 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10605 if (WhiteOnMove(currentMove)) {
10606 GameEnds(BlackWins, "Black mates", GE_FILE);
10608 GameEnds(WhiteWins, "White mates", GE_FILE);
10612 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10618 case MoveNumberOne:
10619 if (lastLoadGameStart == GNUChessGame) {
10620 /* GNUChessGames have numbers, but they aren't move numbers */
10621 if (appData.debugMode)
10622 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10623 yy_text, (int) moveType);
10624 return LoadGameOneMove(EndOfFile); /* tail recursion */
10626 /* else fall thru */
10631 /* Reached start of next game in file */
10632 if (appData.debugMode)
10633 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10634 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10640 if (WhiteOnMove(currentMove)) {
10641 GameEnds(BlackWins, "Black mates", GE_FILE);
10643 GameEnds(WhiteWins, "White mates", GE_FILE);
10647 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10653 case PositionDiagram: /* should not happen; ignore */
10654 case ElapsedTime: /* ignore */
10655 case NAG: /* ignore */
10656 if (appData.debugMode)
10657 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10658 yy_text, (int) moveType);
10659 return LoadGameOneMove(EndOfFile); /* tail recursion */
10662 if (appData.testLegality) {
10663 if (appData.debugMode)
10664 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10665 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10666 (forwardMostMove / 2) + 1,
10667 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10668 DisplayError(move, 0);
10671 if (appData.debugMode)
10672 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10673 yy_text, currentMoveString);
10674 fromX = currentMoveString[0] - AAA;
10675 fromY = currentMoveString[1] - ONE;
10676 toX = currentMoveString[2] - AAA;
10677 toY = currentMoveString[3] - ONE;
10678 promoChar = currentMoveString[4];
10682 case AmbiguousMove:
10683 if (appData.debugMode)
10684 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10685 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10686 (forwardMostMove / 2) + 1,
10687 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10688 DisplayError(move, 0);
10693 case ImpossibleMove:
10694 if (appData.debugMode)
10695 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10696 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10697 (forwardMostMove / 2) + 1,
10698 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10699 DisplayError(move, 0);
10705 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10706 DrawPosition(FALSE, boards[currentMove]);
10707 DisplayBothClocks();
10708 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10709 DisplayComment(currentMove - 1, commentList[currentMove]);
10711 (void) StopLoadGameTimer();
10713 cmailOldMove = forwardMostMove;
10716 /* currentMoveString is set as a side-effect of yylex */
10718 thinkOutput[0] = NULLCHAR;
10719 MakeMove(fromX, fromY, toX, toY, promoChar);
10720 currentMove = forwardMostMove;
10725 /* Load the nth game from the given file */
10727 LoadGameFromFile(filename, n, title, useList)
10731 /*Boolean*/ int useList;
10736 if (strcmp(filename, "-") == 0) {
10740 f = fopen(filename, "rb");
10742 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10743 DisplayError(buf, errno);
10747 if (fseek(f, 0, 0) == -1) {
10748 /* f is not seekable; probably a pipe */
10751 if (useList && n == 0) {
10752 int error = GameListBuild(f);
10754 DisplayError(_("Cannot build game list"), error);
10755 } else if (!ListEmpty(&gameList) &&
10756 ((ListGame *) gameList.tailPred)->number > 1) {
10757 GameListPopUp(f, title);
10764 return LoadGame(f, n, title, FALSE);
10769 MakeRegisteredMove()
10771 int fromX, fromY, toX, toY;
10773 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10774 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10777 if (appData.debugMode)
10778 fprintf(debugFP, "Restoring %s for game %d\n",
10779 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10781 thinkOutput[0] = NULLCHAR;
10782 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10783 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10784 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10785 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10786 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10787 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10788 MakeMove(fromX, fromY, toX, toY, promoChar);
10789 ShowMove(fromX, fromY, toX, toY);
10791 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10798 if (WhiteOnMove(currentMove)) {
10799 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10801 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10806 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10813 if (WhiteOnMove(currentMove)) {
10814 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10816 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10821 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10832 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10834 CmailLoadGame(f, gameNumber, title, useList)
10842 if (gameNumber > nCmailGames) {
10843 DisplayError(_("No more games in this message"), 0);
10846 if (f == lastLoadGameFP) {
10847 int offset = gameNumber - lastLoadGameNumber;
10849 cmailMsg[0] = NULLCHAR;
10850 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10851 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10852 nCmailMovesRegistered--;
10854 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10855 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10856 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10859 if (! RegisterMove()) return FALSE;
10863 retVal = LoadGame(f, gameNumber, title, useList);
10865 /* Make move registered during previous look at this game, if any */
10866 MakeRegisteredMove();
10868 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10869 commentList[currentMove]
10870 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10871 DisplayComment(currentMove - 1, commentList[currentMove]);
10877 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10882 int gameNumber = lastLoadGameNumber + offset;
10883 if (lastLoadGameFP == NULL) {
10884 DisplayError(_("No game has been loaded yet"), 0);
10887 if (gameNumber <= 0) {
10888 DisplayError(_("Can't back up any further"), 0);
10891 if (cmailMsgLoaded) {
10892 return CmailLoadGame(lastLoadGameFP, gameNumber,
10893 lastLoadGameTitle, lastLoadGameUseList);
10895 return LoadGame(lastLoadGameFP, gameNumber,
10896 lastLoadGameTitle, lastLoadGameUseList);
10902 /* Load the nth game from open file f */
10904 LoadGame(f, gameNumber, title, useList)
10912 int gn = gameNumber;
10913 ListGame *lg = NULL;
10914 int numPGNTags = 0;
10916 GameMode oldGameMode;
10917 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10919 if (appData.debugMode)
10920 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10922 if (gameMode == Training )
10923 SetTrainingModeOff();
10925 oldGameMode = gameMode;
10926 if (gameMode != BeginningOfGame) {
10927 Reset(FALSE, TRUE);
10931 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10932 fclose(lastLoadGameFP);
10936 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10939 fseek(f, lg->offset, 0);
10940 GameListHighlight(gameNumber);
10944 DisplayError(_("Game number out of range"), 0);
10949 if (fseek(f, 0, 0) == -1) {
10950 if (f == lastLoadGameFP ?
10951 gameNumber == lastLoadGameNumber + 1 :
10955 DisplayError(_("Can't seek on game file"), 0);
10960 lastLoadGameFP = f;
10961 lastLoadGameNumber = gameNumber;
10962 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10963 lastLoadGameUseList = useList;
10967 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10968 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10969 lg->gameInfo.black);
10971 } else if (*title != NULLCHAR) {
10972 if (gameNumber > 1) {
10973 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10976 DisplayTitle(title);
10980 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10981 gameMode = PlayFromGameFile;
10985 currentMove = forwardMostMove = backwardMostMove = 0;
10986 CopyBoard(boards[0], initialPosition);
10990 * Skip the first gn-1 games in the file.
10991 * Also skip over anything that precedes an identifiable
10992 * start of game marker, to avoid being confused by
10993 * garbage at the start of the file. Currently
10994 * recognized start of game markers are the move number "1",
10995 * the pattern "gnuchess .* game", the pattern
10996 * "^[#;%] [^ ]* game file", and a PGN tag block.
10997 * A game that starts with one of the latter two patterns
10998 * will also have a move number 1, possibly
10999 * following a position diagram.
11000 * 5-4-02: Let's try being more lenient and allowing a game to
11001 * start with an unnumbered move. Does that break anything?
11003 cm = lastLoadGameStart = EndOfFile;
11005 yyboardindex = forwardMostMove;
11006 cm = (ChessMove) Myylex();
11009 if (cmailMsgLoaded) {
11010 nCmailGames = CMAIL_MAX_GAMES - gn;
11013 DisplayError(_("Game not found in file"), 0);
11020 lastLoadGameStart = cm;
11023 case MoveNumberOne:
11024 switch (lastLoadGameStart) {
11029 case MoveNumberOne:
11031 gn--; /* count this game */
11032 lastLoadGameStart = cm;
11041 switch (lastLoadGameStart) {
11044 case MoveNumberOne:
11046 gn--; /* count this game */
11047 lastLoadGameStart = cm;
11050 lastLoadGameStart = cm; /* game counted already */
11058 yyboardindex = forwardMostMove;
11059 cm = (ChessMove) Myylex();
11060 } while (cm == PGNTag || cm == Comment);
11067 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11068 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11069 != CMAIL_OLD_RESULT) {
11071 cmailResult[ CMAIL_MAX_GAMES
11072 - gn - 1] = CMAIL_OLD_RESULT;
11078 /* Only a NormalMove can be at the start of a game
11079 * without a position diagram. */
11080 if (lastLoadGameStart == EndOfFile ) {
11082 lastLoadGameStart = MoveNumberOne;
11091 if (appData.debugMode)
11092 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11094 if (cm == XBoardGame) {
11095 /* Skip any header junk before position diagram and/or move 1 */
11097 yyboardindex = forwardMostMove;
11098 cm = (ChessMove) Myylex();
11100 if (cm == EndOfFile ||
11101 cm == GNUChessGame || cm == XBoardGame) {
11102 /* Empty game; pretend end-of-file and handle later */
11107 if (cm == MoveNumberOne || cm == PositionDiagram ||
11108 cm == PGNTag || cm == Comment)
11111 } else if (cm == GNUChessGame) {
11112 if (gameInfo.event != NULL) {
11113 free(gameInfo.event);
11115 gameInfo.event = StrSave(yy_text);
11118 startedFromSetupPosition = FALSE;
11119 while (cm == PGNTag) {
11120 if (appData.debugMode)
11121 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11122 err = ParsePGNTag(yy_text, &gameInfo);
11123 if (!err) numPGNTags++;
11125 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11126 if(gameInfo.variant != oldVariant) {
11127 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11128 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11129 InitPosition(TRUE);
11130 oldVariant = gameInfo.variant;
11131 if (appData.debugMode)
11132 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11136 if (gameInfo.fen != NULL) {
11137 Board initial_position;
11138 startedFromSetupPosition = TRUE;
11139 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11141 DisplayError(_("Bad FEN position in file"), 0);
11144 CopyBoard(boards[0], initial_position);
11145 if (blackPlaysFirst) {
11146 currentMove = forwardMostMove = backwardMostMove = 1;
11147 CopyBoard(boards[1], initial_position);
11148 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11149 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11150 timeRemaining[0][1] = whiteTimeRemaining;
11151 timeRemaining[1][1] = blackTimeRemaining;
11152 if (commentList[0] != NULL) {
11153 commentList[1] = commentList[0];
11154 commentList[0] = NULL;
11157 currentMove = forwardMostMove = backwardMostMove = 0;
11159 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11161 initialRulePlies = FENrulePlies;
11162 for( i=0; i< nrCastlingRights; i++ )
11163 initialRights[i] = initial_position[CASTLING][i];
11165 yyboardindex = forwardMostMove;
11166 free(gameInfo.fen);
11167 gameInfo.fen = NULL;
11170 yyboardindex = forwardMostMove;
11171 cm = (ChessMove) Myylex();
11173 /* Handle comments interspersed among the tags */
11174 while (cm == Comment) {
11176 if (appData.debugMode)
11177 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11179 AppendComment(currentMove, p, FALSE);
11180 yyboardindex = forwardMostMove;
11181 cm = (ChessMove) Myylex();
11185 /* don't rely on existence of Event tag since if game was
11186 * pasted from clipboard the Event tag may not exist
11188 if (numPGNTags > 0){
11190 if (gameInfo.variant == VariantNormal) {
11191 VariantClass v = StringToVariant(gameInfo.event);
11192 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11193 if(v < VariantShogi) gameInfo.variant = v;
11196 if( appData.autoDisplayTags ) {
11197 tags = PGNTags(&gameInfo);
11198 TagsPopUp(tags, CmailMsg());
11203 /* Make something up, but don't display it now */
11208 if (cm == PositionDiagram) {
11211 Board initial_position;
11213 if (appData.debugMode)
11214 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11216 if (!startedFromSetupPosition) {
11218 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11219 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11230 initial_position[i][j++] = CharToPiece(*p);
11233 while (*p == ' ' || *p == '\t' ||
11234 *p == '\n' || *p == '\r') p++;
11236 if (strncmp(p, "black", strlen("black"))==0)
11237 blackPlaysFirst = TRUE;
11239 blackPlaysFirst = FALSE;
11240 startedFromSetupPosition = TRUE;
11242 CopyBoard(boards[0], initial_position);
11243 if (blackPlaysFirst) {
11244 currentMove = forwardMostMove = backwardMostMove = 1;
11245 CopyBoard(boards[1], initial_position);
11246 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11247 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11248 timeRemaining[0][1] = whiteTimeRemaining;
11249 timeRemaining[1][1] = blackTimeRemaining;
11250 if (commentList[0] != NULL) {
11251 commentList[1] = commentList[0];
11252 commentList[0] = NULL;
11255 currentMove = forwardMostMove = backwardMostMove = 0;
11258 yyboardindex = forwardMostMove;
11259 cm = (ChessMove) Myylex();
11262 if (first.pr == NoProc) {
11263 StartChessProgram(&first);
11265 InitChessProgram(&first, FALSE);
11266 SendToProgram("force\n", &first);
11267 if (startedFromSetupPosition) {
11268 SendBoard(&first, forwardMostMove);
11269 if (appData.debugMode) {
11270 fprintf(debugFP, "Load Game\n");
11272 DisplayBothClocks();
11275 /* [HGM] server: flag to write setup moves in broadcast file as one */
11276 loadFlag = appData.suppressLoadMoves;
11278 while (cm == Comment) {
11280 if (appData.debugMode)
11281 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11283 AppendComment(currentMove, p, FALSE);
11284 yyboardindex = forwardMostMove;
11285 cm = (ChessMove) Myylex();
11288 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11289 cm == WhiteWins || cm == BlackWins ||
11290 cm == GameIsDrawn || cm == GameUnfinished) {
11291 DisplayMessage("", _("No moves in game"));
11292 if (cmailMsgLoaded) {
11293 if (appData.debugMode)
11294 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11298 DrawPosition(FALSE, boards[currentMove]);
11299 DisplayBothClocks();
11300 gameMode = EditGame;
11307 // [HGM] PV info: routine tests if comment empty
11308 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11309 DisplayComment(currentMove - 1, commentList[currentMove]);
11311 if (!matchMode && appData.timeDelay != 0)
11312 DrawPosition(FALSE, boards[currentMove]);
11314 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11315 programStats.ok_to_send = 1;
11318 /* if the first token after the PGN tags is a move
11319 * and not move number 1, retrieve it from the parser
11321 if (cm != MoveNumberOne)
11322 LoadGameOneMove(cm);
11324 /* load the remaining moves from the file */
11325 while (LoadGameOneMove(EndOfFile)) {
11326 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11327 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11330 /* rewind to the start of the game */
11331 currentMove = backwardMostMove;
11333 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11335 if (oldGameMode == AnalyzeFile ||
11336 oldGameMode == AnalyzeMode) {
11337 AnalyzeFileEvent();
11340 if (matchMode || appData.timeDelay == 0) {
11342 gameMode = EditGame;
11344 } else if (appData.timeDelay > 0) {
11345 AutoPlayGameLoop();
11348 if (appData.debugMode)
11349 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11351 loadFlag = 0; /* [HGM] true game starts */
11355 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11357 ReloadPosition(offset)
11360 int positionNumber = lastLoadPositionNumber + offset;
11361 if (lastLoadPositionFP == NULL) {
11362 DisplayError(_("No position has been loaded yet"), 0);
11365 if (positionNumber <= 0) {
11366 DisplayError(_("Can't back up any further"), 0);
11369 return LoadPosition(lastLoadPositionFP, positionNumber,
11370 lastLoadPositionTitle);
11373 /* Load the nth position from the given file */
11375 LoadPositionFromFile(filename, n, title)
11383 if (strcmp(filename, "-") == 0) {
11384 return LoadPosition(stdin, n, "stdin");
11386 f = fopen(filename, "rb");
11388 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11389 DisplayError(buf, errno);
11392 return LoadPosition(f, n, title);
11397 /* Load the nth position from the given open file, and close it */
11399 LoadPosition(f, positionNumber, title)
11401 int positionNumber;
11404 char *p, line[MSG_SIZ];
11405 Board initial_position;
11406 int i, j, fenMode, pn;
11408 if (gameMode == Training )
11409 SetTrainingModeOff();
11411 if (gameMode != BeginningOfGame) {
11412 Reset(FALSE, TRUE);
11414 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11415 fclose(lastLoadPositionFP);
11417 if (positionNumber == 0) positionNumber = 1;
11418 lastLoadPositionFP = f;
11419 lastLoadPositionNumber = positionNumber;
11420 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11421 if (first.pr == NoProc) {
11422 StartChessProgram(&first);
11423 InitChessProgram(&first, FALSE);
11425 pn = positionNumber;
11426 if (positionNumber < 0) {
11427 /* Negative position number means to seek to that byte offset */
11428 if (fseek(f, -positionNumber, 0) == -1) {
11429 DisplayError(_("Can't seek on position file"), 0);
11434 if (fseek(f, 0, 0) == -1) {
11435 if (f == lastLoadPositionFP ?
11436 positionNumber == lastLoadPositionNumber + 1 :
11437 positionNumber == 1) {
11440 DisplayError(_("Can't seek on position file"), 0);
11445 /* See if this file is FEN or old-style xboard */
11446 if (fgets(line, MSG_SIZ, f) == NULL) {
11447 DisplayError(_("Position not found in file"), 0);
11450 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11451 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11454 if (fenMode || line[0] == '#') pn--;
11456 /* skip positions before number pn */
11457 if (fgets(line, MSG_SIZ, f) == NULL) {
11459 DisplayError(_("Position not found in file"), 0);
11462 if (fenMode || line[0] == '#') pn--;
11467 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11468 DisplayError(_("Bad FEN position in file"), 0);
11472 (void) fgets(line, MSG_SIZ, f);
11473 (void) fgets(line, MSG_SIZ, f);
11475 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11476 (void) fgets(line, MSG_SIZ, f);
11477 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11480 initial_position[i][j++] = CharToPiece(*p);
11484 blackPlaysFirst = FALSE;
11486 (void) fgets(line, MSG_SIZ, f);
11487 if (strncmp(line, "black", strlen("black"))==0)
11488 blackPlaysFirst = TRUE;
11491 startedFromSetupPosition = TRUE;
11493 SendToProgram("force\n", &first);
11494 CopyBoard(boards[0], initial_position);
11495 if (blackPlaysFirst) {
11496 currentMove = forwardMostMove = backwardMostMove = 1;
11497 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11498 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11499 CopyBoard(boards[1], initial_position);
11500 DisplayMessage("", _("Black to play"));
11502 currentMove = forwardMostMove = backwardMostMove = 0;
11503 DisplayMessage("", _("White to play"));
11505 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11506 SendBoard(&first, forwardMostMove);
11507 if (appData.debugMode) {
11509 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11510 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11511 fprintf(debugFP, "Load Position\n");
11514 if (positionNumber > 1) {
11515 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11516 DisplayTitle(line);
11518 DisplayTitle(title);
11520 gameMode = EditGame;
11523 timeRemaining[0][1] = whiteTimeRemaining;
11524 timeRemaining[1][1] = blackTimeRemaining;
11525 DrawPosition(FALSE, boards[currentMove]);
11532 CopyPlayerNameIntoFileName(dest, src)
11535 while (*src != NULLCHAR && *src != ',') {
11540 *(*dest)++ = *src++;
11545 char *DefaultFileName(ext)
11548 static char def[MSG_SIZ];
11551 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11553 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11555 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11557 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11564 /* Save the current game to the given file */
11566 SaveGameToFile(filename, append)
11574 if (strcmp(filename, "-") == 0) {
11575 return SaveGame(stdout, 0, NULL);
11577 f = fopen(filename, append ? "a" : "w");
11579 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11580 DisplayError(buf, errno);
11583 safeStrCpy(buf, lastMsg, MSG_SIZ);
11584 DisplayMessage(_("Waiting for access to save file"), "");
11585 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11586 DisplayMessage(_("Saving game"), "");
11587 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11588 result = SaveGame(f, 0, NULL);
11589 DisplayMessage(buf, "");
11599 static char buf[MSG_SIZ];
11602 p = strchr(str, ' ');
11603 if (p == NULL) return str;
11604 strncpy(buf, str, p - str);
11605 buf[p - str] = NULLCHAR;
11609 #define PGN_MAX_LINE 75
11611 #define PGN_SIDE_WHITE 0
11612 #define PGN_SIDE_BLACK 1
11615 static int FindFirstMoveOutOfBook( int side )
11619 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11620 int index = backwardMostMove;
11621 int has_book_hit = 0;
11623 if( (index % 2) != side ) {
11627 while( index < forwardMostMove ) {
11628 /* Check to see if engine is in book */
11629 int depth = pvInfoList[index].depth;
11630 int score = pvInfoList[index].score;
11636 else if( score == 0 && depth == 63 ) {
11637 in_book = 1; /* Zappa */
11639 else if( score == 2 && depth == 99 ) {
11640 in_book = 1; /* Abrok */
11643 has_book_hit += in_book;
11659 void GetOutOfBookInfo( char * buf )
11663 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11665 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11666 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11670 if( oob[0] >= 0 || oob[1] >= 0 ) {
11671 for( i=0; i<2; i++ ) {
11675 if( i > 0 && oob[0] >= 0 ) {
11676 strcat( buf, " " );
11679 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11680 sprintf( buf+strlen(buf), "%s%.2f",
11681 pvInfoList[idx].score >= 0 ? "+" : "",
11682 pvInfoList[idx].score / 100.0 );
11688 /* Save game in PGN style and close the file */
11693 int i, offset, linelen, newblock;
11697 int movelen, numlen, blank;
11698 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11700 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11702 tm = time((time_t *) NULL);
11704 PrintPGNTags(f, &gameInfo);
11706 if (backwardMostMove > 0 || startedFromSetupPosition) {
11707 char *fen = PositionToFEN(backwardMostMove, NULL);
11708 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11709 fprintf(f, "\n{--------------\n");
11710 PrintPosition(f, backwardMostMove);
11711 fprintf(f, "--------------}\n");
11715 /* [AS] Out of book annotation */
11716 if( appData.saveOutOfBookInfo ) {
11719 GetOutOfBookInfo( buf );
11721 if( buf[0] != '\0' ) {
11722 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11729 i = backwardMostMove;
11733 while (i < forwardMostMove) {
11734 /* Print comments preceding this move */
11735 if (commentList[i] != NULL) {
11736 if (linelen > 0) fprintf(f, "\n");
11737 fprintf(f, "%s", commentList[i]);
11742 /* Format move number */
11744 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11747 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11749 numtext[0] = NULLCHAR;
11751 numlen = strlen(numtext);
11754 /* Print move number */
11755 blank = linelen > 0 && numlen > 0;
11756 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11765 fprintf(f, "%s", numtext);
11769 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11770 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11773 blank = linelen > 0 && movelen > 0;
11774 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11783 fprintf(f, "%s", move_buffer);
11784 linelen += movelen;
11786 /* [AS] Add PV info if present */
11787 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11788 /* [HGM] add time */
11789 char buf[MSG_SIZ]; int seconds;
11791 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11797 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11800 seconds = (seconds + 4)/10; // round to full seconds
11802 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11804 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11807 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11808 pvInfoList[i].score >= 0 ? "+" : "",
11809 pvInfoList[i].score / 100.0,
11810 pvInfoList[i].depth,
11813 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11815 /* Print score/depth */
11816 blank = linelen > 0 && movelen > 0;
11817 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11826 fprintf(f, "%s", move_buffer);
11827 linelen += movelen;
11833 /* Start a new line */
11834 if (linelen > 0) fprintf(f, "\n");
11836 /* Print comments after last move */
11837 if (commentList[i] != NULL) {
11838 fprintf(f, "%s\n", commentList[i]);
11842 if (gameInfo.resultDetails != NULL &&
11843 gameInfo.resultDetails[0] != NULLCHAR) {
11844 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11845 PGNResult(gameInfo.result));
11847 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11851 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11855 /* Save game in old style and close the file */
11857 SaveGameOldStyle(f)
11863 tm = time((time_t *) NULL);
11865 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11868 if (backwardMostMove > 0 || startedFromSetupPosition) {
11869 fprintf(f, "\n[--------------\n");
11870 PrintPosition(f, backwardMostMove);
11871 fprintf(f, "--------------]\n");
11876 i = backwardMostMove;
11877 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11879 while (i < forwardMostMove) {
11880 if (commentList[i] != NULL) {
11881 fprintf(f, "[%s]\n", commentList[i]);
11884 if ((i % 2) == 1) {
11885 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11888 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11890 if (commentList[i] != NULL) {
11894 if (i >= forwardMostMove) {
11898 fprintf(f, "%s\n", parseList[i]);
11903 if (commentList[i] != NULL) {
11904 fprintf(f, "[%s]\n", commentList[i]);
11907 /* This isn't really the old style, but it's close enough */
11908 if (gameInfo.resultDetails != NULL &&
11909 gameInfo.resultDetails[0] != NULLCHAR) {
11910 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11911 gameInfo.resultDetails);
11913 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11920 /* Save the current game to open file f and close the file */
11922 SaveGame(f, dummy, dummy2)
11927 if (gameMode == EditPosition) EditPositionDone(TRUE);
11928 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11929 if (appData.oldSaveStyle)
11930 return SaveGameOldStyle(f);
11932 return SaveGamePGN(f);
11935 /* Save the current position to the given file */
11937 SavePositionToFile(filename)
11943 if (strcmp(filename, "-") == 0) {
11944 return SavePosition(stdout, 0, NULL);
11946 f = fopen(filename, "a");
11948 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11949 DisplayError(buf, errno);
11952 safeStrCpy(buf, lastMsg, MSG_SIZ);
11953 DisplayMessage(_("Waiting for access to save file"), "");
11954 flock(fileno(f), LOCK_EX); // [HGM] lock
11955 DisplayMessage(_("Saving position"), "");
11956 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11957 SavePosition(f, 0, NULL);
11958 DisplayMessage(buf, "");
11964 /* Save the current position to the given open file and close the file */
11966 SavePosition(f, dummy, dummy2)
11974 if (gameMode == EditPosition) EditPositionDone(TRUE);
11975 if (appData.oldSaveStyle) {
11976 tm = time((time_t *) NULL);
11978 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11980 fprintf(f, "[--------------\n");
11981 PrintPosition(f, currentMove);
11982 fprintf(f, "--------------]\n");
11984 fen = PositionToFEN(currentMove, NULL);
11985 fprintf(f, "%s\n", fen);
11993 ReloadCmailMsgEvent(unregister)
11997 static char *inFilename = NULL;
11998 static char *outFilename;
12000 struct stat inbuf, outbuf;
12003 /* Any registered moves are unregistered if unregister is set, */
12004 /* i.e. invoked by the signal handler */
12006 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12007 cmailMoveRegistered[i] = FALSE;
12008 if (cmailCommentList[i] != NULL) {
12009 free(cmailCommentList[i]);
12010 cmailCommentList[i] = NULL;
12013 nCmailMovesRegistered = 0;
12016 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12017 cmailResult[i] = CMAIL_NOT_RESULT;
12021 if (inFilename == NULL) {
12022 /* Because the filenames are static they only get malloced once */
12023 /* and they never get freed */
12024 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12025 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12027 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12028 sprintf(outFilename, "%s.out", appData.cmailGameName);
12031 status = stat(outFilename, &outbuf);
12033 cmailMailedMove = FALSE;
12035 status = stat(inFilename, &inbuf);
12036 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12039 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12040 counts the games, notes how each one terminated, etc.
12042 It would be nice to remove this kludge and instead gather all
12043 the information while building the game list. (And to keep it
12044 in the game list nodes instead of having a bunch of fixed-size
12045 parallel arrays.) Note this will require getting each game's
12046 termination from the PGN tags, as the game list builder does
12047 not process the game moves. --mann
12049 cmailMsgLoaded = TRUE;
12050 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12052 /* Load first game in the file or popup game menu */
12053 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12055 #endif /* !WIN32 */
12063 char string[MSG_SIZ];
12065 if ( cmailMailedMove
12066 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12067 return TRUE; /* Allow free viewing */
12070 /* Unregister move to ensure that we don't leave RegisterMove */
12071 /* with the move registered when the conditions for registering no */
12073 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12074 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12075 nCmailMovesRegistered --;
12077 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12079 free(cmailCommentList[lastLoadGameNumber - 1]);
12080 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12084 if (cmailOldMove == -1) {
12085 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12089 if (currentMove > cmailOldMove + 1) {
12090 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12094 if (currentMove < cmailOldMove) {
12095 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12099 if (forwardMostMove > currentMove) {
12100 /* Silently truncate extra moves */
12104 if ( (currentMove == cmailOldMove + 1)
12105 || ( (currentMove == cmailOldMove)
12106 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12107 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12108 if (gameInfo.result != GameUnfinished) {
12109 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12112 if (commentList[currentMove] != NULL) {
12113 cmailCommentList[lastLoadGameNumber - 1]
12114 = StrSave(commentList[currentMove]);
12116 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12118 if (appData.debugMode)
12119 fprintf(debugFP, "Saving %s for game %d\n",
12120 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12122 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12124 f = fopen(string, "w");
12125 if (appData.oldSaveStyle) {
12126 SaveGameOldStyle(f); /* also closes the file */
12128 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12129 f = fopen(string, "w");
12130 SavePosition(f, 0, NULL); /* also closes the file */
12132 fprintf(f, "{--------------\n");
12133 PrintPosition(f, currentMove);
12134 fprintf(f, "--------------}\n\n");
12136 SaveGame(f, 0, NULL); /* also closes the file*/
12139 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12140 nCmailMovesRegistered ++;
12141 } else if (nCmailGames == 1) {
12142 DisplayError(_("You have not made a move yet"), 0);
12153 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12154 FILE *commandOutput;
12155 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12156 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12162 if (! cmailMsgLoaded) {
12163 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12167 if (nCmailGames == nCmailResults) {
12168 DisplayError(_("No unfinished games"), 0);
12172 #if CMAIL_PROHIBIT_REMAIL
12173 if (cmailMailedMove) {
12174 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);
12175 DisplayError(msg, 0);
12180 if (! (cmailMailedMove || RegisterMove())) return;
12182 if ( cmailMailedMove
12183 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12184 snprintf(string, MSG_SIZ, partCommandString,
12185 appData.debugMode ? " -v" : "", appData.cmailGameName);
12186 commandOutput = popen(string, "r");
12188 if (commandOutput == NULL) {
12189 DisplayError(_("Failed to invoke cmail"), 0);
12191 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12192 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12194 if (nBuffers > 1) {
12195 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12196 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12197 nBytes = MSG_SIZ - 1;
12199 (void) memcpy(msg, buffer, nBytes);
12201 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12203 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12204 cmailMailedMove = TRUE; /* Prevent >1 moves */
12207 for (i = 0; i < nCmailGames; i ++) {
12208 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12213 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12215 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12217 appData.cmailGameName,
12219 LoadGameFromFile(buffer, 1, buffer, FALSE);
12220 cmailMsgLoaded = FALSE;
12224 DisplayInformation(msg);
12225 pclose(commandOutput);
12228 if ((*cmailMsg) != '\0') {
12229 DisplayInformation(cmailMsg);
12234 #endif /* !WIN32 */
12243 int prependComma = 0;
12245 char string[MSG_SIZ]; /* Space for game-list */
12248 if (!cmailMsgLoaded) return "";
12250 if (cmailMailedMove) {
12251 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12253 /* Create a list of games left */
12254 snprintf(string, MSG_SIZ, "[");
12255 for (i = 0; i < nCmailGames; i ++) {
12256 if (! ( cmailMoveRegistered[i]
12257 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12258 if (prependComma) {
12259 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12261 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12265 strcat(string, number);
12268 strcat(string, "]");
12270 if (nCmailMovesRegistered + nCmailResults == 0) {
12271 switch (nCmailGames) {
12273 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12277 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12281 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12286 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12288 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12293 if (nCmailResults == nCmailGames) {
12294 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12296 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12301 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12313 if (gameMode == Training)
12314 SetTrainingModeOff();
12317 cmailMsgLoaded = FALSE;
12318 if (appData.icsActive) {
12319 SendToICS(ics_prefix);
12320 SendToICS("refresh\n");
12330 /* Give up on clean exit */
12334 /* Keep trying for clean exit */
12338 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12340 if (telnetISR != NULL) {
12341 RemoveInputSource(telnetISR);
12343 if (icsPR != NoProc) {
12344 DestroyChildProcess(icsPR, TRUE);
12347 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12348 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12350 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12351 /* make sure this other one finishes before killing it! */
12352 if(endingGame) { int count = 0;
12353 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12354 while(endingGame && count++ < 10) DoSleep(1);
12355 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12358 /* Kill off chess programs */
12359 if (first.pr != NoProc) {
12362 DoSleep( appData.delayBeforeQuit );
12363 SendToProgram("quit\n", &first);
12364 DoSleep( appData.delayAfterQuit );
12365 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12367 if (second.pr != NoProc) {
12368 DoSleep( appData.delayBeforeQuit );
12369 SendToProgram("quit\n", &second);
12370 DoSleep( appData.delayAfterQuit );
12371 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12373 if (first.isr != NULL) {
12374 RemoveInputSource(first.isr);
12376 if (second.isr != NULL) {
12377 RemoveInputSource(second.isr);
12380 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12381 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12383 ShutDownFrontEnd();
12390 if (appData.debugMode)
12391 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12395 if (gameMode == MachinePlaysWhite ||
12396 gameMode == MachinePlaysBlack) {
12399 DisplayBothClocks();
12401 if (gameMode == PlayFromGameFile) {
12402 if (appData.timeDelay >= 0)
12403 AutoPlayGameLoop();
12404 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12405 Reset(FALSE, TRUE);
12406 SendToICS(ics_prefix);
12407 SendToICS("refresh\n");
12408 } else if (currentMove < forwardMostMove) {
12409 ForwardInner(forwardMostMove);
12411 pauseExamInvalid = FALSE;
12413 switch (gameMode) {
12417 pauseExamForwardMostMove = forwardMostMove;
12418 pauseExamInvalid = FALSE;
12421 case IcsPlayingWhite:
12422 case IcsPlayingBlack:
12426 case PlayFromGameFile:
12427 (void) StopLoadGameTimer();
12431 case BeginningOfGame:
12432 if (appData.icsActive) return;
12433 /* else fall through */
12434 case MachinePlaysWhite:
12435 case MachinePlaysBlack:
12436 case TwoMachinesPlay:
12437 if (forwardMostMove == 0)
12438 return; /* don't pause if no one has moved */
12439 if ((gameMode == MachinePlaysWhite &&
12440 !WhiteOnMove(forwardMostMove)) ||
12441 (gameMode == MachinePlaysBlack &&
12442 WhiteOnMove(forwardMostMove))) {
12455 char title[MSG_SIZ];
12457 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12458 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12460 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12461 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12462 parseList[currentMove - 1]);
12465 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12472 char *tags = PGNTags(&gameInfo);
12474 EditTagsPopUp(tags, NULL);
12481 if (appData.noChessProgram || gameMode == AnalyzeMode)
12484 if (gameMode != AnalyzeFile) {
12485 if (!appData.icsEngineAnalyze) {
12487 if (gameMode != EditGame) return;
12489 ResurrectChessProgram();
12490 SendToProgram("analyze\n", &first);
12491 first.analyzing = TRUE;
12492 /*first.maybeThinking = TRUE;*/
12493 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12494 EngineOutputPopUp();
12496 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12501 StartAnalysisClock();
12502 GetTimeMark(&lastNodeCountTime);
12509 if (appData.noChessProgram || gameMode == AnalyzeFile)
12512 if (gameMode != AnalyzeMode) {
12514 if (gameMode != EditGame) return;
12515 ResurrectChessProgram();
12516 SendToProgram("analyze\n", &first);
12517 first.analyzing = TRUE;
12518 /*first.maybeThinking = TRUE;*/
12519 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12520 EngineOutputPopUp();
12522 gameMode = AnalyzeFile;
12527 StartAnalysisClock();
12528 GetTimeMark(&lastNodeCountTime);
12533 MachineWhiteEvent()
12536 char *bookHit = NULL;
12538 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12542 if (gameMode == PlayFromGameFile ||
12543 gameMode == TwoMachinesPlay ||
12544 gameMode == Training ||
12545 gameMode == AnalyzeMode ||
12546 gameMode == EndOfGame)
12549 if (gameMode == EditPosition)
12550 EditPositionDone(TRUE);
12552 if (!WhiteOnMove(currentMove)) {
12553 DisplayError(_("It is not White's turn"), 0);
12557 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12560 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12561 gameMode == AnalyzeFile)
12564 ResurrectChessProgram(); /* in case it isn't running */
12565 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12566 gameMode = MachinePlaysWhite;
12569 gameMode = MachinePlaysWhite;
12573 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12575 if (first.sendName) {
12576 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12577 SendToProgram(buf, &first);
12579 if (first.sendTime) {
12580 if (first.useColors) {
12581 SendToProgram("black\n", &first); /*gnu kludge*/
12583 SendTimeRemaining(&first, TRUE);
12585 if (first.useColors) {
12586 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12588 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12589 SetMachineThinkingEnables();
12590 first.maybeThinking = TRUE;
12594 if (appData.autoFlipView && !flipView) {
12595 flipView = !flipView;
12596 DrawPosition(FALSE, NULL);
12597 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12600 if(bookHit) { // [HGM] book: simulate book reply
12601 static char bookMove[MSG_SIZ]; // a bit generous?
12603 programStats.nodes = programStats.depth = programStats.time =
12604 programStats.score = programStats.got_only_move = 0;
12605 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12607 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12608 strcat(bookMove, bookHit);
12609 HandleMachineMove(bookMove, &first);
12614 MachineBlackEvent()
12617 char *bookHit = NULL;
12619 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12623 if (gameMode == PlayFromGameFile ||
12624 gameMode == TwoMachinesPlay ||
12625 gameMode == Training ||
12626 gameMode == AnalyzeMode ||
12627 gameMode == EndOfGame)
12630 if (gameMode == EditPosition)
12631 EditPositionDone(TRUE);
12633 if (WhiteOnMove(currentMove)) {
12634 DisplayError(_("It is not Black's turn"), 0);
12638 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12641 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12642 gameMode == AnalyzeFile)
12645 ResurrectChessProgram(); /* in case it isn't running */
12646 gameMode = MachinePlaysBlack;
12650 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12652 if (first.sendName) {
12653 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12654 SendToProgram(buf, &first);
12656 if (first.sendTime) {
12657 if (first.useColors) {
12658 SendToProgram("white\n", &first); /*gnu kludge*/
12660 SendTimeRemaining(&first, FALSE);
12662 if (first.useColors) {
12663 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12665 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12666 SetMachineThinkingEnables();
12667 first.maybeThinking = TRUE;
12670 if (appData.autoFlipView && flipView) {
12671 flipView = !flipView;
12672 DrawPosition(FALSE, NULL);
12673 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12675 if(bookHit) { // [HGM] book: simulate book reply
12676 static char bookMove[MSG_SIZ]; // a bit generous?
12678 programStats.nodes = programStats.depth = programStats.time =
12679 programStats.score = programStats.got_only_move = 0;
12680 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12682 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12683 strcat(bookMove, bookHit);
12684 HandleMachineMove(bookMove, &first);
12690 DisplayTwoMachinesTitle()
12693 if (appData.matchGames > 0) {
12694 if(appData.tourneyFile[0]) {
12695 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12696 gameInfo.white, gameInfo.black,
12697 nextGame+1, appData.matchGames+1,
12698 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12700 if (first.twoMachinesColor[0] == 'w') {
12701 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12702 gameInfo.white, gameInfo.black,
12703 first.matchWins, second.matchWins,
12704 matchGame - 1 - (first.matchWins + second.matchWins));
12706 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12707 gameInfo.white, gameInfo.black,
12708 second.matchWins, first.matchWins,
12709 matchGame - 1 - (first.matchWins + second.matchWins));
12712 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12718 SettingsMenuIfReady()
12720 if (second.lastPing != second.lastPong) {
12721 DisplayMessage("", _("Waiting for second chess program"));
12722 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12726 DisplayMessage("", "");
12727 SettingsPopUp(&second);
12731 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12734 if (cps->pr == NULL) {
12735 StartChessProgram(cps);
12736 if (cps->protocolVersion == 1) {
12739 /* kludge: allow timeout for initial "feature" command */
12741 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12742 DisplayMessage("", buf);
12743 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12751 TwoMachinesEvent P((void))
12755 ChessProgramState *onmove;
12756 char *bookHit = NULL;
12757 static int stalling = 0;
12761 if (appData.noChessProgram) return;
12763 switch (gameMode) {
12764 case TwoMachinesPlay:
12766 case MachinePlaysWhite:
12767 case MachinePlaysBlack:
12768 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12769 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12773 case BeginningOfGame:
12774 case PlayFromGameFile:
12777 if (gameMode != EditGame) return;
12780 EditPositionDone(TRUE);
12791 // forwardMostMove = currentMove;
12792 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12794 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12796 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12797 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12798 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12802 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12803 SendToProgram("force\n", &second);
12805 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12808 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12809 if(appData.matchPause>10000 || appData.matchPause<10)
12810 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12811 wait = SubtractTimeMarks(&now, &pauseStart);
12812 if(wait < appData.matchPause) {
12813 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12817 DisplayMessage("", "");
12818 if (startedFromSetupPosition) {
12819 SendBoard(&second, backwardMostMove);
12820 if (appData.debugMode) {
12821 fprintf(debugFP, "Two Machines\n");
12824 for (i = backwardMostMove; i < forwardMostMove; i++) {
12825 SendMoveToProgram(i, &second);
12828 gameMode = TwoMachinesPlay;
12832 DisplayTwoMachinesTitle();
12834 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12839 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12840 SendToProgram(first.computerString, &first);
12841 if (first.sendName) {
12842 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12843 SendToProgram(buf, &first);
12845 SendToProgram(second.computerString, &second);
12846 if (second.sendName) {
12847 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12848 SendToProgram(buf, &second);
12852 if (!first.sendTime || !second.sendTime) {
12853 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12854 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12856 if (onmove->sendTime) {
12857 if (onmove->useColors) {
12858 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12860 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12862 if (onmove->useColors) {
12863 SendToProgram(onmove->twoMachinesColor, onmove);
12865 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12866 // SendToProgram("go\n", onmove);
12867 onmove->maybeThinking = TRUE;
12868 SetMachineThinkingEnables();
12872 if(bookHit) { // [HGM] book: simulate book reply
12873 static char bookMove[MSG_SIZ]; // a bit generous?
12875 programStats.nodes = programStats.depth = programStats.time =
12876 programStats.score = programStats.got_only_move = 0;
12877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12879 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12880 strcat(bookMove, bookHit);
12881 savedMessage = bookMove; // args for deferred call
12882 savedState = onmove;
12883 ScheduleDelayedEvent(DeferredBookMove, 1);
12890 if (gameMode == Training) {
12891 SetTrainingModeOff();
12892 gameMode = PlayFromGameFile;
12893 DisplayMessage("", _("Training mode off"));
12895 gameMode = Training;
12896 animateTraining = appData.animate;
12898 /* make sure we are not already at the end of the game */
12899 if (currentMove < forwardMostMove) {
12900 SetTrainingModeOn();
12901 DisplayMessage("", _("Training mode on"));
12903 gameMode = PlayFromGameFile;
12904 DisplayError(_("Already at end of game"), 0);
12913 if (!appData.icsActive) return;
12914 switch (gameMode) {
12915 case IcsPlayingWhite:
12916 case IcsPlayingBlack:
12919 case BeginningOfGame:
12927 EditPositionDone(TRUE);
12940 gameMode = IcsIdle;
12951 switch (gameMode) {
12953 SetTrainingModeOff();
12955 case MachinePlaysWhite:
12956 case MachinePlaysBlack:
12957 case BeginningOfGame:
12958 SendToProgram("force\n", &first);
12959 SetUserThinkingEnables();
12961 case PlayFromGameFile:
12962 (void) StopLoadGameTimer();
12963 if (gameFileFP != NULL) {
12968 EditPositionDone(TRUE);
12973 SendToProgram("force\n", &first);
12975 case TwoMachinesPlay:
12976 GameEnds(EndOfFile, NULL, GE_PLAYER);
12977 ResurrectChessProgram();
12978 SetUserThinkingEnables();
12981 ResurrectChessProgram();
12983 case IcsPlayingBlack:
12984 case IcsPlayingWhite:
12985 DisplayError(_("Warning: You are still playing a game"), 0);
12988 DisplayError(_("Warning: You are still observing a game"), 0);
12991 DisplayError(_("Warning: You are still examining a game"), 0);
13002 first.offeredDraw = second.offeredDraw = 0;
13004 if (gameMode == PlayFromGameFile) {
13005 whiteTimeRemaining = timeRemaining[0][currentMove];
13006 blackTimeRemaining = timeRemaining[1][currentMove];
13010 if (gameMode == MachinePlaysWhite ||
13011 gameMode == MachinePlaysBlack ||
13012 gameMode == TwoMachinesPlay ||
13013 gameMode == EndOfGame) {
13014 i = forwardMostMove;
13015 while (i > currentMove) {
13016 SendToProgram("undo\n", &first);
13019 whiteTimeRemaining = timeRemaining[0][currentMove];
13020 blackTimeRemaining = timeRemaining[1][currentMove];
13021 DisplayBothClocks();
13022 if (whiteFlag || blackFlag) {
13023 whiteFlag = blackFlag = 0;
13028 gameMode = EditGame;
13035 EditPositionEvent()
13037 if (gameMode == EditPosition) {
13043 if (gameMode != EditGame) return;
13045 gameMode = EditPosition;
13048 if (currentMove > 0)
13049 CopyBoard(boards[0], boards[currentMove]);
13051 blackPlaysFirst = !WhiteOnMove(currentMove);
13053 currentMove = forwardMostMove = backwardMostMove = 0;
13054 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13061 /* [DM] icsEngineAnalyze - possible call from other functions */
13062 if (appData.icsEngineAnalyze) {
13063 appData.icsEngineAnalyze = FALSE;
13065 DisplayMessage("",_("Close ICS engine analyze..."));
13067 if (first.analysisSupport && first.analyzing) {
13068 SendToProgram("exit\n", &first);
13069 first.analyzing = FALSE;
13071 thinkOutput[0] = NULLCHAR;
13075 EditPositionDone(Boolean fakeRights)
13077 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13079 startedFromSetupPosition = TRUE;
13080 InitChessProgram(&first, FALSE);
13081 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13082 boards[0][EP_STATUS] = EP_NONE;
13083 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13084 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13085 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13086 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13087 } else boards[0][CASTLING][2] = NoRights;
13088 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13089 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13090 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13091 } else boards[0][CASTLING][5] = NoRights;
13093 SendToProgram("force\n", &first);
13094 if (blackPlaysFirst) {
13095 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13096 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13097 currentMove = forwardMostMove = backwardMostMove = 1;
13098 CopyBoard(boards[1], boards[0]);
13100 currentMove = forwardMostMove = backwardMostMove = 0;
13102 SendBoard(&first, forwardMostMove);
13103 if (appData.debugMode) {
13104 fprintf(debugFP, "EditPosDone\n");
13107 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13108 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13109 gameMode = EditGame;
13111 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13112 ClearHighlights(); /* [AS] */
13115 /* Pause for `ms' milliseconds */
13116 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13126 } while (SubtractTimeMarks(&m2, &m1) < ms);
13129 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13131 SendMultiLineToICS(buf)
13134 char temp[MSG_SIZ+1], *p;
13141 strncpy(temp, buf, len);
13146 if (*p == '\n' || *p == '\r')
13151 strcat(temp, "\n");
13153 SendToPlayer(temp, strlen(temp));
13157 SetWhiteToPlayEvent()
13159 if (gameMode == EditPosition) {
13160 blackPlaysFirst = FALSE;
13161 DisplayBothClocks(); /* works because currentMove is 0 */
13162 } else if (gameMode == IcsExamining) {
13163 SendToICS(ics_prefix);
13164 SendToICS("tomove white\n");
13169 SetBlackToPlayEvent()
13171 if (gameMode == EditPosition) {
13172 blackPlaysFirst = TRUE;
13173 currentMove = 1; /* kludge */
13174 DisplayBothClocks();
13176 } else if (gameMode == IcsExamining) {
13177 SendToICS(ics_prefix);
13178 SendToICS("tomove black\n");
13183 EditPositionMenuEvent(selection, x, y)
13184 ChessSquare selection;
13188 ChessSquare piece = boards[0][y][x];
13190 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13192 switch (selection) {
13194 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13195 SendToICS(ics_prefix);
13196 SendToICS("bsetup clear\n");
13197 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13198 SendToICS(ics_prefix);
13199 SendToICS("clearboard\n");
13201 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13202 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13203 for (y = 0; y < BOARD_HEIGHT; y++) {
13204 if (gameMode == IcsExamining) {
13205 if (boards[currentMove][y][x] != EmptySquare) {
13206 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13211 boards[0][y][x] = p;
13216 if (gameMode == EditPosition) {
13217 DrawPosition(FALSE, boards[0]);
13222 SetWhiteToPlayEvent();
13226 SetBlackToPlayEvent();
13230 if (gameMode == IcsExamining) {
13231 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13232 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13235 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13236 if(x == BOARD_LEFT-2) {
13237 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13238 boards[0][y][1] = 0;
13240 if(x == BOARD_RGHT+1) {
13241 if(y >= gameInfo.holdingsSize) break;
13242 boards[0][y][BOARD_WIDTH-2] = 0;
13245 boards[0][y][x] = EmptySquare;
13246 DrawPosition(FALSE, boards[0]);
13251 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13252 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13253 selection = (ChessSquare) (PROMOTED piece);
13254 } else if(piece == EmptySquare) selection = WhiteSilver;
13255 else selection = (ChessSquare)((int)piece - 1);
13259 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13260 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13261 selection = (ChessSquare) (DEMOTED piece);
13262 } else if(piece == EmptySquare) selection = BlackSilver;
13263 else selection = (ChessSquare)((int)piece + 1);
13268 if(gameInfo.variant == VariantShatranj ||
13269 gameInfo.variant == VariantXiangqi ||
13270 gameInfo.variant == VariantCourier ||
13271 gameInfo.variant == VariantMakruk )
13272 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13277 if(gameInfo.variant == VariantXiangqi)
13278 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13279 if(gameInfo.variant == VariantKnightmate)
13280 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13283 if (gameMode == IcsExamining) {
13284 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13285 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13286 PieceToChar(selection), AAA + x, ONE + y);
13289 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13291 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13292 n = PieceToNumber(selection - BlackPawn);
13293 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13294 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13295 boards[0][BOARD_HEIGHT-1-n][1]++;
13297 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13298 n = PieceToNumber(selection);
13299 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13300 boards[0][n][BOARD_WIDTH-1] = selection;
13301 boards[0][n][BOARD_WIDTH-2]++;
13304 boards[0][y][x] = selection;
13305 DrawPosition(TRUE, boards[0]);
13313 DropMenuEvent(selection, x, y)
13314 ChessSquare selection;
13317 ChessMove moveType;
13319 switch (gameMode) {
13320 case IcsPlayingWhite:
13321 case MachinePlaysBlack:
13322 if (!WhiteOnMove(currentMove)) {
13323 DisplayMoveError(_("It is Black's turn"));
13326 moveType = WhiteDrop;
13328 case IcsPlayingBlack:
13329 case MachinePlaysWhite:
13330 if (WhiteOnMove(currentMove)) {
13331 DisplayMoveError(_("It is White's turn"));
13334 moveType = BlackDrop;
13337 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13343 if (moveType == BlackDrop && selection < BlackPawn) {
13344 selection = (ChessSquare) ((int) selection
13345 + (int) BlackPawn - (int) WhitePawn);
13347 if (boards[currentMove][y][x] != EmptySquare) {
13348 DisplayMoveError(_("That square is occupied"));
13352 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13358 /* Accept a pending offer of any kind from opponent */
13360 if (appData.icsActive) {
13361 SendToICS(ics_prefix);
13362 SendToICS("accept\n");
13363 } else if (cmailMsgLoaded) {
13364 if (currentMove == cmailOldMove &&
13365 commentList[cmailOldMove] != NULL &&
13366 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13367 "Black offers a draw" : "White offers a draw")) {
13369 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13370 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13372 DisplayError(_("There is no pending offer on this move"), 0);
13373 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13376 /* Not used for offers from chess program */
13383 /* Decline a pending offer of any kind from opponent */
13385 if (appData.icsActive) {
13386 SendToICS(ics_prefix);
13387 SendToICS("decline\n");
13388 } else if (cmailMsgLoaded) {
13389 if (currentMove == cmailOldMove &&
13390 commentList[cmailOldMove] != NULL &&
13391 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13392 "Black offers a draw" : "White offers a draw")) {
13394 AppendComment(cmailOldMove, "Draw declined", TRUE);
13395 DisplayComment(cmailOldMove - 1, "Draw declined");
13398 DisplayError(_("There is no pending offer on this move"), 0);
13401 /* Not used for offers from chess program */
13408 /* Issue ICS rematch command */
13409 if (appData.icsActive) {
13410 SendToICS(ics_prefix);
13411 SendToICS("rematch\n");
13418 /* Call your opponent's flag (claim a win on time) */
13419 if (appData.icsActive) {
13420 SendToICS(ics_prefix);
13421 SendToICS("flag\n");
13423 switch (gameMode) {
13426 case MachinePlaysWhite:
13429 GameEnds(GameIsDrawn, "Both players ran out of time",
13432 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13434 DisplayError(_("Your opponent is not out of time"), 0);
13437 case MachinePlaysBlack:
13440 GameEnds(GameIsDrawn, "Both players ran out of time",
13443 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13445 DisplayError(_("Your opponent is not out of time"), 0);
13453 ClockClick(int which)
13454 { // [HGM] code moved to back-end from winboard.c
13455 if(which) { // black clock
13456 if (gameMode == EditPosition || gameMode == IcsExamining) {
13457 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13458 SetBlackToPlayEvent();
13459 } else if (gameMode == EditGame || shiftKey) {
13460 AdjustClock(which, -1);
13461 } else if (gameMode == IcsPlayingWhite ||
13462 gameMode == MachinePlaysBlack) {
13465 } else { // white clock
13466 if (gameMode == EditPosition || gameMode == IcsExamining) {
13467 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13468 SetWhiteToPlayEvent();
13469 } else if (gameMode == EditGame || shiftKey) {
13470 AdjustClock(which, -1);
13471 } else if (gameMode == IcsPlayingBlack ||
13472 gameMode == MachinePlaysWhite) {
13481 /* Offer draw or accept pending draw offer from opponent */
13483 if (appData.icsActive) {
13484 /* Note: tournament rules require draw offers to be
13485 made after you make your move but before you punch
13486 your clock. Currently ICS doesn't let you do that;
13487 instead, you immediately punch your clock after making
13488 a move, but you can offer a draw at any time. */
13490 SendToICS(ics_prefix);
13491 SendToICS("draw\n");
13492 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13493 } else if (cmailMsgLoaded) {
13494 if (currentMove == cmailOldMove &&
13495 commentList[cmailOldMove] != NULL &&
13496 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13497 "Black offers a draw" : "White offers a draw")) {
13498 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13500 } else if (currentMove == cmailOldMove + 1) {
13501 char *offer = WhiteOnMove(cmailOldMove) ?
13502 "White offers a draw" : "Black offers a draw";
13503 AppendComment(currentMove, offer, TRUE);
13504 DisplayComment(currentMove - 1, offer);
13505 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13507 DisplayError(_("You must make your move before offering a draw"), 0);
13508 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13510 } else if (first.offeredDraw) {
13511 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13513 if (first.sendDrawOffers) {
13514 SendToProgram("draw\n", &first);
13515 userOfferedDraw = TRUE;
13523 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13525 if (appData.icsActive) {
13526 SendToICS(ics_prefix);
13527 SendToICS("adjourn\n");
13529 /* Currently GNU Chess doesn't offer or accept Adjourns */
13537 /* Offer Abort or accept pending Abort offer from opponent */
13539 if (appData.icsActive) {
13540 SendToICS(ics_prefix);
13541 SendToICS("abort\n");
13543 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13550 /* Resign. You can do this even if it's not your turn. */
13552 if (appData.icsActive) {
13553 SendToICS(ics_prefix);
13554 SendToICS("resign\n");
13556 switch (gameMode) {
13557 case MachinePlaysWhite:
13558 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13560 case MachinePlaysBlack:
13561 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13564 if (cmailMsgLoaded) {
13566 if (WhiteOnMove(cmailOldMove)) {
13567 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13569 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13571 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13582 StopObservingEvent()
13584 /* Stop observing current games */
13585 SendToICS(ics_prefix);
13586 SendToICS("unobserve\n");
13590 StopExaminingEvent()
13592 /* Stop observing current game */
13593 SendToICS(ics_prefix);
13594 SendToICS("unexamine\n");
13598 ForwardInner(target)
13603 if (appData.debugMode)
13604 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13605 target, currentMove, forwardMostMove);
13607 if (gameMode == EditPosition)
13610 if (gameMode == PlayFromGameFile && !pausing)
13613 if (gameMode == IcsExamining && pausing)
13614 limit = pauseExamForwardMostMove;
13616 limit = forwardMostMove;
13618 if (target > limit) target = limit;
13620 if (target > 0 && moveList[target - 1][0]) {
13621 int fromX, fromY, toX, toY;
13622 toX = moveList[target - 1][2] - AAA;
13623 toY = moveList[target - 1][3] - ONE;
13624 if (moveList[target - 1][1] == '@') {
13625 if (appData.highlightLastMove) {
13626 SetHighlights(-1, -1, toX, toY);
13629 fromX = moveList[target - 1][0] - AAA;
13630 fromY = moveList[target - 1][1] - ONE;
13631 if (target == currentMove + 1) {
13632 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13634 if (appData.highlightLastMove) {
13635 SetHighlights(fromX, fromY, toX, toY);
13639 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13640 gameMode == Training || gameMode == PlayFromGameFile ||
13641 gameMode == AnalyzeFile) {
13642 while (currentMove < target) {
13643 SendMoveToProgram(currentMove++, &first);
13646 currentMove = target;
13649 if (gameMode == EditGame || gameMode == EndOfGame) {
13650 whiteTimeRemaining = timeRemaining[0][currentMove];
13651 blackTimeRemaining = timeRemaining[1][currentMove];
13653 DisplayBothClocks();
13654 DisplayMove(currentMove - 1);
13655 DrawPosition(FALSE, boards[currentMove]);
13656 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13657 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13658 DisplayComment(currentMove - 1, commentList[currentMove]);
13660 DisplayBook(currentMove);
13667 if (gameMode == IcsExamining && !pausing) {
13668 SendToICS(ics_prefix);
13669 SendToICS("forward\n");
13671 ForwardInner(currentMove + 1);
13678 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13679 /* to optimze, we temporarily turn off analysis mode while we feed
13680 * the remaining moves to the engine. Otherwise we get analysis output
13683 if (first.analysisSupport) {
13684 SendToProgram("exit\nforce\n", &first);
13685 first.analyzing = FALSE;
13689 if (gameMode == IcsExamining && !pausing) {
13690 SendToICS(ics_prefix);
13691 SendToICS("forward 999999\n");
13693 ForwardInner(forwardMostMove);
13696 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13697 /* we have fed all the moves, so reactivate analysis mode */
13698 SendToProgram("analyze\n", &first);
13699 first.analyzing = TRUE;
13700 /*first.maybeThinking = TRUE;*/
13701 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13706 BackwardInner(target)
13709 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13711 if (appData.debugMode)
13712 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13713 target, currentMove, forwardMostMove);
13715 if (gameMode == EditPosition) return;
13716 if (currentMove <= backwardMostMove) {
13718 DrawPosition(full_redraw, boards[currentMove]);
13721 if (gameMode == PlayFromGameFile && !pausing)
13724 if (moveList[target][0]) {
13725 int fromX, fromY, toX, toY;
13726 toX = moveList[target][2] - AAA;
13727 toY = moveList[target][3] - ONE;
13728 if (moveList[target][1] == '@') {
13729 if (appData.highlightLastMove) {
13730 SetHighlights(-1, -1, toX, toY);
13733 fromX = moveList[target][0] - AAA;
13734 fromY = moveList[target][1] - ONE;
13735 if (target == currentMove - 1) {
13736 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13738 if (appData.highlightLastMove) {
13739 SetHighlights(fromX, fromY, toX, toY);
13743 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13744 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13745 while (currentMove > target) {
13746 SendToProgram("undo\n", &first);
13750 currentMove = target;
13753 if (gameMode == EditGame || gameMode == EndOfGame) {
13754 whiteTimeRemaining = timeRemaining[0][currentMove];
13755 blackTimeRemaining = timeRemaining[1][currentMove];
13757 DisplayBothClocks();
13758 DisplayMove(currentMove - 1);
13759 DrawPosition(full_redraw, boards[currentMove]);
13760 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13761 // [HGM] PV info: routine tests if comment empty
13762 DisplayComment(currentMove - 1, commentList[currentMove]);
13763 DisplayBook(currentMove);
13769 if (gameMode == IcsExamining && !pausing) {
13770 SendToICS(ics_prefix);
13771 SendToICS("backward\n");
13773 BackwardInner(currentMove - 1);
13780 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13781 /* to optimize, we temporarily turn off analysis mode while we undo
13782 * all the moves. Otherwise we get analysis output after each undo.
13784 if (first.analysisSupport) {
13785 SendToProgram("exit\nforce\n", &first);
13786 first.analyzing = FALSE;
13790 if (gameMode == IcsExamining && !pausing) {
13791 SendToICS(ics_prefix);
13792 SendToICS("backward 999999\n");
13794 BackwardInner(backwardMostMove);
13797 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13798 /* we have fed all the moves, so reactivate analysis mode */
13799 SendToProgram("analyze\n", &first);
13800 first.analyzing = TRUE;
13801 /*first.maybeThinking = TRUE;*/
13802 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13809 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13810 if (to >= forwardMostMove) to = forwardMostMove;
13811 if (to <= backwardMostMove) to = backwardMostMove;
13812 if (to < currentMove) {
13820 RevertEvent(Boolean annotate)
13822 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13825 if (gameMode != IcsExamining) {
13826 DisplayError(_("You are not examining a game"), 0);
13830 DisplayError(_("You can't revert while pausing"), 0);
13833 SendToICS(ics_prefix);
13834 SendToICS("revert\n");
13840 switch (gameMode) {
13841 case MachinePlaysWhite:
13842 case MachinePlaysBlack:
13843 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13844 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13847 if (forwardMostMove < 2) return;
13848 currentMove = forwardMostMove = forwardMostMove - 2;
13849 whiteTimeRemaining = timeRemaining[0][currentMove];
13850 blackTimeRemaining = timeRemaining[1][currentMove];
13851 DisplayBothClocks();
13852 DisplayMove(currentMove - 1);
13853 ClearHighlights();/*!! could figure this out*/
13854 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13855 SendToProgram("remove\n", &first);
13856 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13859 case BeginningOfGame:
13863 case IcsPlayingWhite:
13864 case IcsPlayingBlack:
13865 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13866 SendToICS(ics_prefix);
13867 SendToICS("takeback 2\n");
13869 SendToICS(ics_prefix);
13870 SendToICS("takeback 1\n");
13879 ChessProgramState *cps;
13881 switch (gameMode) {
13882 case MachinePlaysWhite:
13883 if (!WhiteOnMove(forwardMostMove)) {
13884 DisplayError(_("It is your turn"), 0);
13889 case MachinePlaysBlack:
13890 if (WhiteOnMove(forwardMostMove)) {
13891 DisplayError(_("It is your turn"), 0);
13896 case TwoMachinesPlay:
13897 if (WhiteOnMove(forwardMostMove) ==
13898 (first.twoMachinesColor[0] == 'w')) {
13904 case BeginningOfGame:
13908 SendToProgram("?\n", cps);
13912 TruncateGameEvent()
13915 if (gameMode != EditGame) return;
13922 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13923 if (forwardMostMove > currentMove) {
13924 if (gameInfo.resultDetails != NULL) {
13925 free(gameInfo.resultDetails);
13926 gameInfo.resultDetails = NULL;
13927 gameInfo.result = GameUnfinished;
13929 forwardMostMove = currentMove;
13930 HistorySet(parseList, backwardMostMove, forwardMostMove,
13938 if (appData.noChessProgram) return;
13939 switch (gameMode) {
13940 case MachinePlaysWhite:
13941 if (WhiteOnMove(forwardMostMove)) {
13942 DisplayError(_("Wait until your turn"), 0);
13946 case BeginningOfGame:
13947 case MachinePlaysBlack:
13948 if (!WhiteOnMove(forwardMostMove)) {
13949 DisplayError(_("Wait until your turn"), 0);
13954 DisplayError(_("No hint available"), 0);
13957 SendToProgram("hint\n", &first);
13958 hintRequested = TRUE;
13964 if (appData.noChessProgram) return;
13965 switch (gameMode) {
13966 case MachinePlaysWhite:
13967 if (WhiteOnMove(forwardMostMove)) {
13968 DisplayError(_("Wait until your turn"), 0);
13972 case BeginningOfGame:
13973 case MachinePlaysBlack:
13974 if (!WhiteOnMove(forwardMostMove)) {
13975 DisplayError(_("Wait until your turn"), 0);
13980 EditPositionDone(TRUE);
13982 case TwoMachinesPlay:
13987 SendToProgram("bk\n", &first);
13988 bookOutput[0] = NULLCHAR;
13989 bookRequested = TRUE;
13995 char *tags = PGNTags(&gameInfo);
13996 TagsPopUp(tags, CmailMsg());
14000 /* end button procedures */
14003 PrintPosition(fp, move)
14009 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14010 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14011 char c = PieceToChar(boards[move][i][j]);
14012 fputc(c == 'x' ? '.' : c, fp);
14013 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14016 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14017 fprintf(fp, "white to play\n");
14019 fprintf(fp, "black to play\n");
14026 if (gameInfo.white != NULL) {
14027 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14033 /* Find last component of program's own name, using some heuristics */
14035 TidyProgramName(prog, host, buf)
14036 char *prog, *host, buf[MSG_SIZ];
14039 int local = (strcmp(host, "localhost") == 0);
14040 while (!local && (p = strchr(prog, ';')) != NULL) {
14042 while (*p == ' ') p++;
14045 if (*prog == '"' || *prog == '\'') {
14046 q = strchr(prog + 1, *prog);
14048 q = strchr(prog, ' ');
14050 if (q == NULL) q = prog + strlen(prog);
14052 while (p >= prog && *p != '/' && *p != '\\') p--;
14054 if(p == prog && *p == '"') p++;
14055 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14056 memcpy(buf, p, q - p);
14057 buf[q - p] = NULLCHAR;
14065 TimeControlTagValue()
14068 if (!appData.clockMode) {
14069 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14070 } else if (movesPerSession > 0) {
14071 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14072 } else if (timeIncrement == 0) {
14073 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14075 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14077 return StrSave(buf);
14083 /* This routine is used only for certain modes */
14084 VariantClass v = gameInfo.variant;
14085 ChessMove r = GameUnfinished;
14088 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14089 r = gameInfo.result;
14090 p = gameInfo.resultDetails;
14091 gameInfo.resultDetails = NULL;
14093 ClearGameInfo(&gameInfo);
14094 gameInfo.variant = v;
14096 switch (gameMode) {
14097 case MachinePlaysWhite:
14098 gameInfo.event = StrSave( appData.pgnEventHeader );
14099 gameInfo.site = StrSave(HostName());
14100 gameInfo.date = PGNDate();
14101 gameInfo.round = StrSave("-");
14102 gameInfo.white = StrSave(first.tidy);
14103 gameInfo.black = StrSave(UserName());
14104 gameInfo.timeControl = TimeControlTagValue();
14107 case MachinePlaysBlack:
14108 gameInfo.event = StrSave( appData.pgnEventHeader );
14109 gameInfo.site = StrSave(HostName());
14110 gameInfo.date = PGNDate();
14111 gameInfo.round = StrSave("-");
14112 gameInfo.white = StrSave(UserName());
14113 gameInfo.black = StrSave(first.tidy);
14114 gameInfo.timeControl = TimeControlTagValue();
14117 case TwoMachinesPlay:
14118 gameInfo.event = StrSave( appData.pgnEventHeader );
14119 gameInfo.site = StrSave(HostName());
14120 gameInfo.date = PGNDate();
14123 snprintf(buf, MSG_SIZ, "%d", roundNr);
14124 gameInfo.round = StrSave(buf);
14126 gameInfo.round = StrSave("-");
14128 if (first.twoMachinesColor[0] == 'w') {
14129 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14130 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14132 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14133 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14135 gameInfo.timeControl = TimeControlTagValue();
14139 gameInfo.event = StrSave("Edited game");
14140 gameInfo.site = StrSave(HostName());
14141 gameInfo.date = PGNDate();
14142 gameInfo.round = StrSave("-");
14143 gameInfo.white = StrSave("-");
14144 gameInfo.black = StrSave("-");
14145 gameInfo.result = r;
14146 gameInfo.resultDetails = p;
14150 gameInfo.event = StrSave("Edited position");
14151 gameInfo.site = StrSave(HostName());
14152 gameInfo.date = PGNDate();
14153 gameInfo.round = StrSave("-");
14154 gameInfo.white = StrSave("-");
14155 gameInfo.black = StrSave("-");
14158 case IcsPlayingWhite:
14159 case IcsPlayingBlack:
14164 case PlayFromGameFile:
14165 gameInfo.event = StrSave("Game from non-PGN file");
14166 gameInfo.site = StrSave(HostName());
14167 gameInfo.date = PGNDate();
14168 gameInfo.round = StrSave("-");
14169 gameInfo.white = StrSave("?");
14170 gameInfo.black = StrSave("?");
14179 ReplaceComment(index, text)
14187 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14188 pvInfoList[index-1].depth == len &&
14189 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14190 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14191 while (*text == '\n') text++;
14192 len = strlen(text);
14193 while (len > 0 && text[len - 1] == '\n') len--;
14195 if (commentList[index] != NULL)
14196 free(commentList[index]);
14199 commentList[index] = NULL;
14202 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14203 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14204 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14205 commentList[index] = (char *) malloc(len + 2);
14206 strncpy(commentList[index], text, len);
14207 commentList[index][len] = '\n';
14208 commentList[index][len + 1] = NULLCHAR;
14210 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14212 commentList[index] = (char *) malloc(len + 7);
14213 safeStrCpy(commentList[index], "{\n", 3);
14214 safeStrCpy(commentList[index]+2, text, len+1);
14215 commentList[index][len+2] = NULLCHAR;
14216 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14217 strcat(commentList[index], "\n}\n");
14231 if (ch == '\r') continue;
14233 } while (ch != '\0');
14237 AppendComment(index, text, addBraces)
14240 Boolean addBraces; // [HGM] braces: tells if we should add {}
14245 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14246 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14249 while (*text == '\n') text++;
14250 len = strlen(text);
14251 while (len > 0 && text[len - 1] == '\n') len--;
14253 if (len == 0) return;
14255 if (commentList[index] != NULL) {
14256 old = commentList[index];
14257 oldlen = strlen(old);
14258 while(commentList[index][oldlen-1] == '\n')
14259 commentList[index][--oldlen] = NULLCHAR;
14260 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14261 safeStrCpy(commentList[index], old, oldlen + len + 6);
14263 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14264 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14265 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14266 while (*text == '\n') { text++; len--; }
14267 commentList[index][--oldlen] = NULLCHAR;
14269 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14270 else strcat(commentList[index], "\n");
14271 strcat(commentList[index], text);
14272 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14273 else strcat(commentList[index], "\n");
14275 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14277 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14278 else commentList[index][0] = NULLCHAR;
14279 strcat(commentList[index], text);
14280 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14281 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14285 static char * FindStr( char * text, char * sub_text )
14287 char * result = strstr( text, sub_text );
14289 if( result != NULL ) {
14290 result += strlen( sub_text );
14296 /* [AS] Try to extract PV info from PGN comment */
14297 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14298 char *GetInfoFromComment( int index, char * text )
14300 char * sep = text, *p;
14302 if( text != NULL && index > 0 ) {
14305 int time = -1, sec = 0, deci;
14306 char * s_eval = FindStr( text, "[%eval " );
14307 char * s_emt = FindStr( text, "[%emt " );
14309 if( s_eval != NULL || s_emt != NULL ) {
14313 if( s_eval != NULL ) {
14314 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14318 if( delim != ']' ) {
14323 if( s_emt != NULL ) {
14328 /* We expect something like: [+|-]nnn.nn/dd */
14331 if(*text != '{') return text; // [HGM] braces: must be normal comment
14333 sep = strchr( text, '/' );
14334 if( sep == NULL || sep < (text+4) ) {
14339 if(p[1] == '(') { // comment starts with PV
14340 p = strchr(p, ')'); // locate end of PV
14341 if(p == NULL || sep < p+5) return text;
14342 // at this point we have something like "{(.*) +0.23/6 ..."
14343 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14344 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14345 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14347 time = -1; sec = -1; deci = -1;
14348 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14349 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14350 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14351 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14355 if( score_lo < 0 || score_lo >= 100 ) {
14359 if(sec >= 0) time = 600*time + 10*sec; else
14360 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14362 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14364 /* [HGM] PV time: now locate end of PV info */
14365 while( *++sep >= '0' && *sep <= '9'); // strip depth
14367 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14369 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14371 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14372 while(*sep == ' ') sep++;
14383 pvInfoList[index-1].depth = depth;
14384 pvInfoList[index-1].score = score;
14385 pvInfoList[index-1].time = 10*time; // centi-sec
14386 if(*sep == '}') *sep = 0; else *--sep = '{';
14387 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14393 SendToProgram(message, cps)
14395 ChessProgramState *cps;
14397 int count, outCount, error;
14400 if (cps->pr == NULL) return;
14403 if (appData.debugMode) {
14406 fprintf(debugFP, "%ld >%-6s: %s",
14407 SubtractTimeMarks(&now, &programStartTime),
14408 cps->which, message);
14411 count = strlen(message);
14412 outCount = OutputToProcess(cps->pr, message, count, &error);
14413 if (outCount < count && !exiting
14414 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14415 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14416 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14417 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14418 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14419 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14420 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14421 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14423 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14424 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14425 gameInfo.result = res;
14427 gameInfo.resultDetails = StrSave(buf);
14429 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14430 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14435 ReceiveFromProgram(isr, closure, message, count, error)
14436 InputSourceRef isr;
14444 ChessProgramState *cps = (ChessProgramState *)closure;
14446 if (isr != cps->isr) return; /* Killed intentionally */
14449 RemoveInputSource(cps->isr);
14450 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14451 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14452 _(cps->which), cps->program);
14453 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14454 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14455 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14456 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14457 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14459 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14460 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14461 gameInfo.result = res;
14463 gameInfo.resultDetails = StrSave(buf);
14465 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14466 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14468 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14469 _(cps->which), cps->program);
14470 RemoveInputSource(cps->isr);
14472 /* [AS] Program is misbehaving badly... kill it */
14473 if( count == -2 ) {
14474 DestroyChildProcess( cps->pr, 9 );
14478 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14483 if ((end_str = strchr(message, '\r')) != NULL)
14484 *end_str = NULLCHAR;
14485 if ((end_str = strchr(message, '\n')) != NULL)
14486 *end_str = NULLCHAR;
14488 if (appData.debugMode) {
14489 TimeMark now; int print = 1;
14490 char *quote = ""; char c; int i;
14492 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14493 char start = message[0];
14494 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14495 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14496 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14497 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14498 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14499 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14500 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14501 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14502 sscanf(message, "hint: %c", &c)!=1 &&
14503 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14504 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14505 print = (appData.engineComments >= 2);
14507 message[0] = start; // restore original message
14511 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14512 SubtractTimeMarks(&now, &programStartTime), cps->which,
14518 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14519 if (appData.icsEngineAnalyze) {
14520 if (strstr(message, "whisper") != NULL ||
14521 strstr(message, "kibitz") != NULL ||
14522 strstr(message, "tellics") != NULL) return;
14525 HandleMachineMove(message, cps);
14530 SendTimeControl(cps, mps, tc, inc, sd, st)
14531 ChessProgramState *cps;
14532 int mps, inc, sd, st;
14538 if( timeControl_2 > 0 ) {
14539 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14540 tc = timeControl_2;
14543 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14544 inc /= cps->timeOdds;
14545 st /= cps->timeOdds;
14547 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14550 /* Set exact time per move, normally using st command */
14551 if (cps->stKludge) {
14552 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14554 if (seconds == 0) {
14555 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14557 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14560 snprintf(buf, MSG_SIZ, "st %d\n", st);
14563 /* Set conventional or incremental time control, using level command */
14564 if (seconds == 0) {
14565 /* Note old gnuchess bug -- minutes:seconds used to not work.
14566 Fixed in later versions, but still avoid :seconds
14567 when seconds is 0. */
14568 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14570 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14571 seconds, inc/1000.);
14574 SendToProgram(buf, cps);
14576 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14577 /* Orthogonally, limit search to given depth */
14579 if (cps->sdKludge) {
14580 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14582 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14584 SendToProgram(buf, cps);
14587 if(cps->nps >= 0) { /* [HGM] nps */
14588 if(cps->supportsNPS == FALSE)
14589 cps->nps = -1; // don't use if engine explicitly says not supported!
14591 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14592 SendToProgram(buf, cps);
14597 ChessProgramState *WhitePlayer()
14598 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14600 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14601 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14607 SendTimeRemaining(cps, machineWhite)
14608 ChessProgramState *cps;
14609 int /*boolean*/ machineWhite;
14611 char message[MSG_SIZ];
14614 /* Note: this routine must be called when the clocks are stopped
14615 or when they have *just* been set or switched; otherwise
14616 it will be off by the time since the current tick started.
14618 if (machineWhite) {
14619 time = whiteTimeRemaining / 10;
14620 otime = blackTimeRemaining / 10;
14622 time = blackTimeRemaining / 10;
14623 otime = whiteTimeRemaining / 10;
14625 /* [HGM] translate opponent's time by time-odds factor */
14626 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14627 if (appData.debugMode) {
14628 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14631 if (time <= 0) time = 1;
14632 if (otime <= 0) otime = 1;
14634 snprintf(message, MSG_SIZ, "time %ld\n", time);
14635 SendToProgram(message, cps);
14637 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14638 SendToProgram(message, cps);
14642 BoolFeature(p, name, loc, cps)
14646 ChessProgramState *cps;
14649 int len = strlen(name);
14652 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14654 sscanf(*p, "%d", &val);
14656 while (**p && **p != ' ')
14658 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14659 SendToProgram(buf, cps);
14666 IntFeature(p, name, loc, cps)
14670 ChessProgramState *cps;
14673 int len = strlen(name);
14674 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14676 sscanf(*p, "%d", loc);
14677 while (**p && **p != ' ') (*p)++;
14678 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14679 SendToProgram(buf, cps);
14686 StringFeature(p, name, loc, cps)
14690 ChessProgramState *cps;
14693 int len = strlen(name);
14694 if (strncmp((*p), name, len) == 0
14695 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14697 sscanf(*p, "%[^\"]", loc);
14698 while (**p && **p != '\"') (*p)++;
14699 if (**p == '\"') (*p)++;
14700 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14701 SendToProgram(buf, cps);
14708 ParseOption(Option *opt, ChessProgramState *cps)
14709 // [HGM] options: process the string that defines an engine option, and determine
14710 // name, type, default value, and allowed value range
14712 char *p, *q, buf[MSG_SIZ];
14713 int n, min = (-1)<<31, max = 1<<31, def;
14715 if(p = strstr(opt->name, " -spin ")) {
14716 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14717 if(max < min) max = min; // enforce consistency
14718 if(def < min) def = min;
14719 if(def > max) def = max;
14724 } else if((p = strstr(opt->name, " -slider "))) {
14725 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14726 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14727 if(max < min) max = min; // enforce consistency
14728 if(def < min) def = min;
14729 if(def > max) def = max;
14733 opt->type = Spin; // Slider;
14734 } else if((p = strstr(opt->name, " -string "))) {
14735 opt->textValue = p+9;
14736 opt->type = TextBox;
14737 } else if((p = strstr(opt->name, " -file "))) {
14738 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14739 opt->textValue = p+7;
14740 opt->type = FileName; // FileName;
14741 } else if((p = strstr(opt->name, " -path "))) {
14742 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14743 opt->textValue = p+7;
14744 opt->type = PathName; // PathName;
14745 } else if(p = strstr(opt->name, " -check ")) {
14746 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14747 opt->value = (def != 0);
14748 opt->type = CheckBox;
14749 } else if(p = strstr(opt->name, " -combo ")) {
14750 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14751 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14752 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14753 opt->value = n = 0;
14754 while(q = StrStr(q, " /// ")) {
14755 n++; *q = 0; // count choices, and null-terminate each of them
14757 if(*q == '*') { // remember default, which is marked with * prefix
14761 cps->comboList[cps->comboCnt++] = q;
14763 cps->comboList[cps->comboCnt++] = NULL;
14765 opt->type = ComboBox;
14766 } else if(p = strstr(opt->name, " -button")) {
14767 opt->type = Button;
14768 } else if(p = strstr(opt->name, " -save")) {
14769 opt->type = SaveButton;
14770 } else return FALSE;
14771 *p = 0; // terminate option name
14772 // now look if the command-line options define a setting for this engine option.
14773 if(cps->optionSettings && cps->optionSettings[0])
14774 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14775 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14776 snprintf(buf, MSG_SIZ, "option %s", p);
14777 if(p = strstr(buf, ",")) *p = 0;
14778 if(q = strchr(buf, '=')) switch(opt->type) {
14780 for(n=0; n<opt->max; n++)
14781 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14784 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14788 opt->value = atoi(q+1);
14793 SendToProgram(buf, cps);
14799 FeatureDone(cps, val)
14800 ChessProgramState* cps;
14803 DelayedEventCallback cb = GetDelayedEvent();
14804 if ((cb == InitBackEnd3 && cps == &first) ||
14805 (cb == SettingsMenuIfReady && cps == &second) ||
14806 (cb == LoadEngine) ||
14807 (cb == TwoMachinesEventIfReady)) {
14808 CancelDelayedEvent();
14809 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14811 cps->initDone = val;
14814 /* Parse feature command from engine */
14816 ParseFeatures(args, cps)
14818 ChessProgramState *cps;
14826 while (*p == ' ') p++;
14827 if (*p == NULLCHAR) return;
14829 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14830 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14831 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14832 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14833 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14834 if (BoolFeature(&p, "reuse", &val, cps)) {
14835 /* Engine can disable reuse, but can't enable it if user said no */
14836 if (!val) cps->reuse = FALSE;
14839 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14840 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14841 if (gameMode == TwoMachinesPlay) {
14842 DisplayTwoMachinesTitle();
14848 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14849 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14850 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14851 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14852 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14853 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14854 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14855 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14856 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14857 if (IntFeature(&p, "done", &val, cps)) {
14858 FeatureDone(cps, val);
14861 /* Added by Tord: */
14862 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14863 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14864 /* End of additions by Tord */
14866 /* [HGM] added features: */
14867 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14868 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14869 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14870 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14871 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14872 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14873 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14874 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14875 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14876 SendToProgram(buf, cps);
14879 if(cps->nrOptions >= MAX_OPTIONS) {
14881 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14882 DisplayError(buf, 0);
14886 /* End of additions by HGM */
14888 /* unknown feature: complain and skip */
14890 while (*q && *q != '=') q++;
14891 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14892 SendToProgram(buf, cps);
14898 while (*p && *p != '\"') p++;
14899 if (*p == '\"') p++;
14901 while (*p && *p != ' ') p++;
14909 PeriodicUpdatesEvent(newState)
14912 if (newState == appData.periodicUpdates)
14915 appData.periodicUpdates=newState;
14917 /* Display type changes, so update it now */
14918 // DisplayAnalysis();
14920 /* Get the ball rolling again... */
14922 AnalysisPeriodicEvent(1);
14923 StartAnalysisClock();
14928 PonderNextMoveEvent(newState)
14931 if (newState == appData.ponderNextMove) return;
14932 if (gameMode == EditPosition) EditPositionDone(TRUE);
14934 SendToProgram("hard\n", &first);
14935 if (gameMode == TwoMachinesPlay) {
14936 SendToProgram("hard\n", &second);
14939 SendToProgram("easy\n", &first);
14940 thinkOutput[0] = NULLCHAR;
14941 if (gameMode == TwoMachinesPlay) {
14942 SendToProgram("easy\n", &second);
14945 appData.ponderNextMove = newState;
14949 NewSettingEvent(option, feature, command, value)
14951 int option, value, *feature;
14955 if (gameMode == EditPosition) EditPositionDone(TRUE);
14956 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14957 if(feature == NULL || *feature) SendToProgram(buf, &first);
14958 if (gameMode == TwoMachinesPlay) {
14959 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14964 ShowThinkingEvent()
14965 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14967 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14968 int newState = appData.showThinking
14969 // [HGM] thinking: other features now need thinking output as well
14970 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14972 if (oldState == newState) return;
14973 oldState = newState;
14974 if (gameMode == EditPosition) EditPositionDone(TRUE);
14976 SendToProgram("post\n", &first);
14977 if (gameMode == TwoMachinesPlay) {
14978 SendToProgram("post\n", &second);
14981 SendToProgram("nopost\n", &first);
14982 thinkOutput[0] = NULLCHAR;
14983 if (gameMode == TwoMachinesPlay) {
14984 SendToProgram("nopost\n", &second);
14987 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14991 AskQuestionEvent(title, question, replyPrefix, which)
14992 char *title; char *question; char *replyPrefix; char *which;
14994 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14995 if (pr == NoProc) return;
14996 AskQuestion(title, question, replyPrefix, pr);
15000 TypeInEvent(char firstChar)
15002 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
15003 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
15004 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
15005 gameMode == EditPosition || gameMode == IcsExamining ||
\r
15006 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
15007 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
15008 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
15009 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
15010 gameMode == Training) PopUpMoveDialog(firstChar);
15014 TypeInDoneEvent(char *move)
15017 int n, fromX, fromY, toX, toY;
15019 ChessMove moveType;
\r
15022 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
15023 EditPositionPasteFEN(move);
\r
15026 // [HGM] movenum: allow move number to be typed in any mode
\r
15027 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
15028 ToNrEvent(2*n-1);
\r
15032 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
15033 gameMode != Training) {
\r
15034 DisplayMoveError(_("Displayed move is not current"));
\r
15036 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15037 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
15038 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
15039 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15040 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
15041 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
15043 DisplayMoveError(_("Could not parse move"));
\r
15049 DisplayMove(moveNumber)
15052 char message[MSG_SIZ];
15054 char cpThinkOutput[MSG_SIZ];
15056 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15058 if (moveNumber == forwardMostMove - 1 ||
15059 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15061 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15063 if (strchr(cpThinkOutput, '\n')) {
15064 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15067 *cpThinkOutput = NULLCHAR;
15070 /* [AS] Hide thinking from human user */
15071 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15072 *cpThinkOutput = NULLCHAR;
15073 if( thinkOutput[0] != NULLCHAR ) {
15076 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15077 cpThinkOutput[i] = '.';
15079 cpThinkOutput[i] = NULLCHAR;
15080 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15084 if (moveNumber == forwardMostMove - 1 &&
15085 gameInfo.resultDetails != NULL) {
15086 if (gameInfo.resultDetails[0] == NULLCHAR) {
15087 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15089 snprintf(res, MSG_SIZ, " {%s} %s",
15090 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15096 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15097 DisplayMessage(res, cpThinkOutput);
15099 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15100 WhiteOnMove(moveNumber) ? " " : ".. ",
15101 parseList[moveNumber], res);
15102 DisplayMessage(message, cpThinkOutput);
15107 DisplayComment(moveNumber, text)
15111 char title[MSG_SIZ];
15112 char buf[8000]; // comment can be long!
15115 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15116 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15118 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15119 WhiteOnMove(moveNumber) ? " " : ".. ",
15120 parseList[moveNumber]);
15122 // [HGM] PV info: display PV info together with (or as) comment
15123 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15124 if(text == NULL) text = "";
15125 score = pvInfoList[moveNumber].score;
15126 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15127 depth, (pvInfoList[moveNumber].time+50)/100, text);
15130 if (text != NULL && (appData.autoDisplayComment || commentUp))
15131 CommentPopUp(title, text);
15134 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15135 * might be busy thinking or pondering. It can be omitted if your
15136 * gnuchess is configured to stop thinking immediately on any user
15137 * input. However, that gnuchess feature depends on the FIONREAD
15138 * ioctl, which does not work properly on some flavors of Unix.
15142 ChessProgramState *cps;
15145 if (!cps->useSigint) return;
15146 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15147 switch (gameMode) {
15148 case MachinePlaysWhite:
15149 case MachinePlaysBlack:
15150 case TwoMachinesPlay:
15151 case IcsPlayingWhite:
15152 case IcsPlayingBlack:
15155 /* Skip if we know it isn't thinking */
15156 if (!cps->maybeThinking) return;
15157 if (appData.debugMode)
15158 fprintf(debugFP, "Interrupting %s\n", cps->which);
15159 InterruptChildProcess(cps->pr);
15160 cps->maybeThinking = FALSE;
15165 #endif /*ATTENTION*/
15171 if (whiteTimeRemaining <= 0) {
15174 if (appData.icsActive) {
15175 if (appData.autoCallFlag &&
15176 gameMode == IcsPlayingBlack && !blackFlag) {
15177 SendToICS(ics_prefix);
15178 SendToICS("flag\n");
15182 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15184 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15185 if (appData.autoCallFlag) {
15186 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15193 if (blackTimeRemaining <= 0) {
15196 if (appData.icsActive) {
15197 if (appData.autoCallFlag &&
15198 gameMode == IcsPlayingWhite && !whiteFlag) {
15199 SendToICS(ics_prefix);
15200 SendToICS("flag\n");
15204 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15206 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15207 if (appData.autoCallFlag) {
15208 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15221 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15222 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15225 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15227 if ( !WhiteOnMove(forwardMostMove) ) {
15228 /* White made time control */
15229 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15230 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15231 /* [HGM] time odds: correct new time quota for time odds! */
15232 / WhitePlayer()->timeOdds;
15233 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15235 lastBlack -= blackTimeRemaining;
15236 /* Black made time control */
15237 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15238 / WhitePlayer()->other->timeOdds;
15239 lastWhite = whiteTimeRemaining;
15244 DisplayBothClocks()
15246 int wom = gameMode == EditPosition ?
15247 !blackPlaysFirst : WhiteOnMove(currentMove);
15248 DisplayWhiteClock(whiteTimeRemaining, wom);
15249 DisplayBlackClock(blackTimeRemaining, !wom);
15253 /* Timekeeping seems to be a portability nightmare. I think everyone
15254 has ftime(), but I'm really not sure, so I'm including some ifdefs
15255 to use other calls if you don't. Clocks will be less accurate if
15256 you have neither ftime nor gettimeofday.
15259 /* VS 2008 requires the #include outside of the function */
15260 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15261 #include <sys/timeb.h>
15264 /* Get the current time as a TimeMark */
15269 #if HAVE_GETTIMEOFDAY
15271 struct timeval timeVal;
15272 struct timezone timeZone;
15274 gettimeofday(&timeVal, &timeZone);
15275 tm->sec = (long) timeVal.tv_sec;
15276 tm->ms = (int) (timeVal.tv_usec / 1000L);
15278 #else /*!HAVE_GETTIMEOFDAY*/
15281 // include <sys/timeb.h> / moved to just above start of function
15282 struct timeb timeB;
15285 tm->sec = (long) timeB.time;
15286 tm->ms = (int) timeB.millitm;
15288 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15289 tm->sec = (long) time(NULL);
15295 /* Return the difference in milliseconds between two
15296 time marks. We assume the difference will fit in a long!
15299 SubtractTimeMarks(tm2, tm1)
15300 TimeMark *tm2, *tm1;
15302 return 1000L*(tm2->sec - tm1->sec) +
15303 (long) (tm2->ms - tm1->ms);
15308 * Code to manage the game clocks.
15310 * In tournament play, black starts the clock and then white makes a move.
15311 * We give the human user a slight advantage if he is playing white---the
15312 * clocks don't run until he makes his first move, so it takes zero time.
15313 * Also, we don't account for network lag, so we could get out of sync
15314 * with GNU Chess's clock -- but then, referees are always right.
15317 static TimeMark tickStartTM;
15318 static long intendedTickLength;
15321 NextTickLength(timeRemaining)
15322 long timeRemaining;
15324 long nominalTickLength, nextTickLength;
15326 if (timeRemaining > 0L && timeRemaining <= 10000L)
15327 nominalTickLength = 100L;
15329 nominalTickLength = 1000L;
15330 nextTickLength = timeRemaining % nominalTickLength;
15331 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15333 return nextTickLength;
15336 /* Adjust clock one minute up or down */
15338 AdjustClock(Boolean which, int dir)
15340 if(which) blackTimeRemaining += 60000*dir;
15341 else whiteTimeRemaining += 60000*dir;
15342 DisplayBothClocks();
15345 /* Stop clocks and reset to a fresh time control */
15349 (void) StopClockTimer();
15350 if (appData.icsActive) {
15351 whiteTimeRemaining = blackTimeRemaining = 0;
15352 } else if (searchTime) {
15353 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15354 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15355 } else { /* [HGM] correct new time quote for time odds */
15356 whiteTC = blackTC = fullTimeControlString;
15357 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15358 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15360 if (whiteFlag || blackFlag) {
15362 whiteFlag = blackFlag = FALSE;
15364 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15365 DisplayBothClocks();
15368 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15370 /* Decrement running clock by amount of time that has passed */
15374 long timeRemaining;
15375 long lastTickLength, fudge;
15378 if (!appData.clockMode) return;
15379 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15383 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15385 /* Fudge if we woke up a little too soon */
15386 fudge = intendedTickLength - lastTickLength;
15387 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15389 if (WhiteOnMove(forwardMostMove)) {
15390 if(whiteNPS >= 0) lastTickLength = 0;
15391 timeRemaining = whiteTimeRemaining -= lastTickLength;
15392 if(timeRemaining < 0 && !appData.icsActive) {
15393 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15394 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15395 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15396 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15399 DisplayWhiteClock(whiteTimeRemaining - fudge,
15400 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15402 if(blackNPS >= 0) lastTickLength = 0;
15403 timeRemaining = blackTimeRemaining -= lastTickLength;
15404 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15405 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15407 blackStartMove = forwardMostMove;
15408 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15411 DisplayBlackClock(blackTimeRemaining - fudge,
15412 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15414 if (CheckFlags()) return;
15417 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15418 StartClockTimer(intendedTickLength);
15420 /* if the time remaining has fallen below the alarm threshold, sound the
15421 * alarm. if the alarm has sounded and (due to a takeback or time control
15422 * with increment) the time remaining has increased to a level above the
15423 * threshold, reset the alarm so it can sound again.
15426 if (appData.icsActive && appData.icsAlarm) {
15428 /* make sure we are dealing with the user's clock */
15429 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15430 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15433 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15434 alarmSounded = FALSE;
15435 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15437 alarmSounded = TRUE;
15443 /* A player has just moved, so stop the previously running
15444 clock and (if in clock mode) start the other one.
15445 We redisplay both clocks in case we're in ICS mode, because
15446 ICS gives us an update to both clocks after every move.
15447 Note that this routine is called *after* forwardMostMove
15448 is updated, so the last fractional tick must be subtracted
15449 from the color that is *not* on move now.
15452 SwitchClocks(int newMoveNr)
15454 long lastTickLength;
15456 int flagged = FALSE;
15460 if (StopClockTimer() && appData.clockMode) {
15461 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15462 if (!WhiteOnMove(forwardMostMove)) {
15463 if(blackNPS >= 0) lastTickLength = 0;
15464 blackTimeRemaining -= lastTickLength;
15465 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15466 // if(pvInfoList[forwardMostMove].time == -1)
15467 pvInfoList[forwardMostMove].time = // use GUI time
15468 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15470 if(whiteNPS >= 0) lastTickLength = 0;
15471 whiteTimeRemaining -= lastTickLength;
15472 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15473 // if(pvInfoList[forwardMostMove].time == -1)
15474 pvInfoList[forwardMostMove].time =
15475 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15477 flagged = CheckFlags();
15479 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15480 CheckTimeControl();
15482 if (flagged || !appData.clockMode) return;
15484 switch (gameMode) {
15485 case MachinePlaysBlack:
15486 case MachinePlaysWhite:
15487 case BeginningOfGame:
15488 if (pausing) return;
15492 case PlayFromGameFile:
15500 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15501 if(WhiteOnMove(forwardMostMove))
15502 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15503 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15507 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15508 whiteTimeRemaining : blackTimeRemaining);
15509 StartClockTimer(intendedTickLength);
15513 /* Stop both clocks */
15517 long lastTickLength;
15520 if (!StopClockTimer()) return;
15521 if (!appData.clockMode) return;
15525 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15526 if (WhiteOnMove(forwardMostMove)) {
15527 if(whiteNPS >= 0) lastTickLength = 0;
15528 whiteTimeRemaining -= lastTickLength;
15529 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15531 if(blackNPS >= 0) lastTickLength = 0;
15532 blackTimeRemaining -= lastTickLength;
15533 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15538 /* Start clock of player on move. Time may have been reset, so
15539 if clock is already running, stop and restart it. */
15543 (void) StopClockTimer(); /* in case it was running already */
15544 DisplayBothClocks();
15545 if (CheckFlags()) return;
15547 if (!appData.clockMode) return;
15548 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15550 GetTimeMark(&tickStartTM);
15551 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15552 whiteTimeRemaining : blackTimeRemaining);
15554 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15555 whiteNPS = blackNPS = -1;
15556 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15557 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15558 whiteNPS = first.nps;
15559 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15560 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15561 blackNPS = first.nps;
15562 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15563 whiteNPS = second.nps;
15564 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15565 blackNPS = second.nps;
15566 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15568 StartClockTimer(intendedTickLength);
15575 long second, minute, hour, day;
15577 static char buf[32];
15579 if (ms > 0 && ms <= 9900) {
15580 /* convert milliseconds to tenths, rounding up */
15581 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15583 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15587 /* convert milliseconds to seconds, rounding up */
15588 /* use floating point to avoid strangeness of integer division
15589 with negative dividends on many machines */
15590 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15597 day = second / (60 * 60 * 24);
15598 second = second % (60 * 60 * 24);
15599 hour = second / (60 * 60);
15600 second = second % (60 * 60);
15601 minute = second / 60;
15602 second = second % 60;
15605 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15606 sign, day, hour, minute, second);
15608 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15610 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15617 * This is necessary because some C libraries aren't ANSI C compliant yet.
15620 StrStr(string, match)
15621 char *string, *match;
15625 length = strlen(match);
15627 for (i = strlen(string) - length; i >= 0; i--, string++)
15628 if (!strncmp(match, string, length))
15635 StrCaseStr(string, match)
15636 char *string, *match;
15640 length = strlen(match);
15642 for (i = strlen(string) - length; i >= 0; i--, string++) {
15643 for (j = 0; j < length; j++) {
15644 if (ToLower(match[j]) != ToLower(string[j]))
15647 if (j == length) return string;
15661 c1 = ToLower(*s1++);
15662 c2 = ToLower(*s2++);
15663 if (c1 > c2) return 1;
15664 if (c1 < c2) return -1;
15665 if (c1 == NULLCHAR) return 0;
15674 return isupper(c) ? tolower(c) : c;
15682 return islower(c) ? toupper(c) : c;
15684 #endif /* !_amigados */
15692 if ((ret = (char *) malloc(strlen(s) + 1)))
15694 safeStrCpy(ret, s, strlen(s)+1);
15700 StrSavePtr(s, savePtr)
15701 char *s, **savePtr;
15706 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15707 safeStrCpy(*savePtr, s, strlen(s)+1);
15719 clock = time((time_t *)NULL);
15720 tm = localtime(&clock);
15721 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15722 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15723 return StrSave(buf);
15728 PositionToFEN(move, overrideCastling)
15730 char *overrideCastling;
15732 int i, j, fromX, fromY, toX, toY;
15739 whiteToPlay = (gameMode == EditPosition) ?
15740 !blackPlaysFirst : (move % 2 == 0);
15743 /* Piece placement data */
15744 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15746 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15747 if (boards[move][i][j] == EmptySquare) {
15749 } else { ChessSquare piece = boards[move][i][j];
15750 if (emptycount > 0) {
15751 if(emptycount<10) /* [HGM] can be >= 10 */
15752 *p++ = '0' + emptycount;
15753 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15756 if(PieceToChar(piece) == '+') {
15757 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15759 piece = (ChessSquare)(DEMOTED piece);
15761 *p++ = PieceToChar(piece);
15763 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15764 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15769 if (emptycount > 0) {
15770 if(emptycount<10) /* [HGM] can be >= 10 */
15771 *p++ = '0' + emptycount;
15772 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15779 /* [HGM] print Crazyhouse or Shogi holdings */
15780 if( gameInfo.holdingsWidth ) {
15781 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15783 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15784 piece = boards[move][i][BOARD_WIDTH-1];
15785 if( piece != EmptySquare )
15786 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15787 *p++ = PieceToChar(piece);
15789 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15790 piece = boards[move][BOARD_HEIGHT-i-1][0];
15791 if( piece != EmptySquare )
15792 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15793 *p++ = PieceToChar(piece);
15796 if( q == p ) *p++ = '-';
15802 *p++ = whiteToPlay ? 'w' : 'b';
15805 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15806 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15808 if(nrCastlingRights) {
15810 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15811 /* [HGM] write directly from rights */
15812 if(boards[move][CASTLING][2] != NoRights &&
15813 boards[move][CASTLING][0] != NoRights )
15814 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15815 if(boards[move][CASTLING][2] != NoRights &&
15816 boards[move][CASTLING][1] != NoRights )
15817 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15818 if(boards[move][CASTLING][5] != NoRights &&
15819 boards[move][CASTLING][3] != NoRights )
15820 *p++ = boards[move][CASTLING][3] + AAA;
15821 if(boards[move][CASTLING][5] != NoRights &&
15822 boards[move][CASTLING][4] != NoRights )
15823 *p++ = boards[move][CASTLING][4] + AAA;
15826 /* [HGM] write true castling rights */
15827 if( nrCastlingRights == 6 ) {
15828 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15829 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15830 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15831 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15832 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15833 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15834 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15835 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15838 if (q == p) *p++ = '-'; /* No castling rights */
15842 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15843 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15844 /* En passant target square */
15845 if (move > backwardMostMove) {
15846 fromX = moveList[move - 1][0] - AAA;
15847 fromY = moveList[move - 1][1] - ONE;
15848 toX = moveList[move - 1][2] - AAA;
15849 toY = moveList[move - 1][3] - ONE;
15850 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15851 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15852 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15854 /* 2-square pawn move just happened */
15856 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15860 } else if(move == backwardMostMove) {
15861 // [HGM] perhaps we should always do it like this, and forget the above?
15862 if((signed char)boards[move][EP_STATUS] >= 0) {
15863 *p++ = boards[move][EP_STATUS] + AAA;
15864 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15875 /* [HGM] find reversible plies */
15876 { int i = 0, j=move;
15878 if (appData.debugMode) { int k;
15879 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15880 for(k=backwardMostMove; k<=forwardMostMove; k++)
15881 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15885 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15886 if( j == backwardMostMove ) i += initialRulePlies;
15887 sprintf(p, "%d ", i);
15888 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15890 /* Fullmove number */
15891 sprintf(p, "%d", (move / 2) + 1);
15893 return StrSave(buf);
15897 ParseFEN(board, blackPlaysFirst, fen)
15899 int *blackPlaysFirst;
15909 /* [HGM] by default clear Crazyhouse holdings, if present */
15910 if(gameInfo.holdingsWidth) {
15911 for(i=0; i<BOARD_HEIGHT; i++) {
15912 board[i][0] = EmptySquare; /* black holdings */
15913 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15914 board[i][1] = (ChessSquare) 0; /* black counts */
15915 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15919 /* Piece placement data */
15920 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15923 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15924 if (*p == '/') p++;
15925 emptycount = gameInfo.boardWidth - j;
15926 while (emptycount--)
15927 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15929 #if(BOARD_FILES >= 10)
15930 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15931 p++; emptycount=10;
15932 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15933 while (emptycount--)
15934 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15936 } else if (isdigit(*p)) {
15937 emptycount = *p++ - '0';
15938 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15939 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15940 while (emptycount--)
15941 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15942 } else if (*p == '+' || isalpha(*p)) {
15943 if (j >= gameInfo.boardWidth) return FALSE;
15945 piece = CharToPiece(*++p);
15946 if(piece == EmptySquare) return FALSE; /* unknown piece */
15947 piece = (ChessSquare) (PROMOTED piece ); p++;
15948 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15949 } else piece = CharToPiece(*p++);
15951 if(piece==EmptySquare) return FALSE; /* unknown piece */
15952 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15953 piece = (ChessSquare) (PROMOTED piece);
15954 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15957 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15963 while (*p == '/' || *p == ' ') p++;
15965 /* [HGM] look for Crazyhouse holdings here */
15966 while(*p==' ') p++;
15967 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15969 if(*p == '-' ) p++; /* empty holdings */ else {
15970 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15971 /* if we would allow FEN reading to set board size, we would */
15972 /* have to add holdings and shift the board read so far here */
15973 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15975 if((int) piece >= (int) BlackPawn ) {
15976 i = (int)piece - (int)BlackPawn;
15977 i = PieceToNumber((ChessSquare)i);
15978 if( i >= gameInfo.holdingsSize ) return FALSE;
15979 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15980 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15982 i = (int)piece - (int)WhitePawn;
15983 i = PieceToNumber((ChessSquare)i);
15984 if( i >= gameInfo.holdingsSize ) return FALSE;
15985 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15986 board[i][BOARD_WIDTH-2]++; /* black holdings */
15993 while(*p == ' ') p++;
15997 if(appData.colorNickNames) {
15998 if( c == appData.colorNickNames[0] ) c = 'w'; else
15999 if( c == appData.colorNickNames[1] ) c = 'b';
16003 *blackPlaysFirst = FALSE;
16006 *blackPlaysFirst = TRUE;
16012 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16013 /* return the extra info in global variiables */
16015 /* set defaults in case FEN is incomplete */
16016 board[EP_STATUS] = EP_UNKNOWN;
16017 for(i=0; i<nrCastlingRights; i++ ) {
16018 board[CASTLING][i] =
16019 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16020 } /* assume possible unless obviously impossible */
16021 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16022 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16023 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16024 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16025 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16026 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16027 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16028 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16031 while(*p==' ') p++;
16032 if(nrCastlingRights) {
16033 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16034 /* castling indicator present, so default becomes no castlings */
16035 for(i=0; i<nrCastlingRights; i++ ) {
16036 board[CASTLING][i] = NoRights;
16039 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16040 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16041 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16042 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16043 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16045 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16046 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16047 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16049 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16050 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16051 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16052 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16053 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16054 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16057 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16058 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16059 board[CASTLING][2] = whiteKingFile;
16062 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16063 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16064 board[CASTLING][2] = whiteKingFile;
16067 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16068 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16069 board[CASTLING][5] = blackKingFile;
16072 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16073 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16074 board[CASTLING][5] = blackKingFile;
16077 default: /* FRC castlings */
16078 if(c >= 'a') { /* black rights */
16079 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16080 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16081 if(i == BOARD_RGHT) break;
16082 board[CASTLING][5] = i;
16084 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16085 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16087 board[CASTLING][3] = c;
16089 board[CASTLING][4] = c;
16090 } else { /* white rights */
16091 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16092 if(board[0][i] == WhiteKing) break;
16093 if(i == BOARD_RGHT) break;
16094 board[CASTLING][2] = i;
16095 c -= AAA - 'a' + 'A';
16096 if(board[0][c] >= WhiteKing) break;
16098 board[CASTLING][0] = c;
16100 board[CASTLING][1] = c;
16104 for(i=0; i<nrCastlingRights; i++)
16105 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16106 if (appData.debugMode) {
16107 fprintf(debugFP, "FEN castling rights:");
16108 for(i=0; i<nrCastlingRights; i++)
16109 fprintf(debugFP, " %d", board[CASTLING][i]);
16110 fprintf(debugFP, "\n");
16113 while(*p==' ') p++;
16116 /* read e.p. field in games that know e.p. capture */
16117 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16118 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16120 p++; board[EP_STATUS] = EP_NONE;
16122 char c = *p++ - AAA;
16124 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16125 if(*p >= '0' && *p <='9') p++;
16126 board[EP_STATUS] = c;
16131 if(sscanf(p, "%d", &i) == 1) {
16132 FENrulePlies = i; /* 50-move ply counter */
16133 /* (The move number is still ignored) */
16140 EditPositionPasteFEN(char *fen)
16143 Board initial_position;
16145 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16146 DisplayError(_("Bad FEN position in clipboard"), 0);
16149 int savedBlackPlaysFirst = blackPlaysFirst;
16150 EditPositionEvent();
16151 blackPlaysFirst = savedBlackPlaysFirst;
16152 CopyBoard(boards[0], initial_position);
16153 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16154 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16155 DisplayBothClocks();
16156 DrawPosition(FALSE, boards[currentMove]);
16161 static char cseq[12] = "\\ ";
16163 Boolean set_cont_sequence(char *new_seq)
16168 // handle bad attempts to set the sequence
16170 return 0; // acceptable error - no debug
16172 len = strlen(new_seq);
16173 ret = (len > 0) && (len < sizeof(cseq));
16175 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16176 else if (appData.debugMode)
16177 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16182 reformat a source message so words don't cross the width boundary. internal
16183 newlines are not removed. returns the wrapped size (no null character unless
16184 included in source message). If dest is NULL, only calculate the size required
16185 for the dest buffer. lp argument indicats line position upon entry, and it's
16186 passed back upon exit.
16188 int wrap(char *dest, char *src, int count, int width, int *lp)
16190 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16192 cseq_len = strlen(cseq);
16193 old_line = line = *lp;
16194 ansi = len = clen = 0;
16196 for (i=0; i < count; i++)
16198 if (src[i] == '\033')
16201 // if we hit the width, back up
16202 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16204 // store i & len in case the word is too long
16205 old_i = i, old_len = len;
16207 // find the end of the last word
16208 while (i && src[i] != ' ' && src[i] != '\n')
16214 // word too long? restore i & len before splitting it
16215 if ((old_i-i+clen) >= width)
16222 if (i && src[i-1] == ' ')
16225 if (src[i] != ' ' && src[i] != '\n')
16232 // now append the newline and continuation sequence
16237 strncpy(dest+len, cseq, cseq_len);
16245 dest[len] = src[i];
16249 if (src[i] == '\n')
16254 if (dest && appData.debugMode)
16256 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16257 count, width, line, len, *lp);
16258 show_bytes(debugFP, src, count);
16259 fprintf(debugFP, "\ndest: ");
16260 show_bytes(debugFP, dest, len);
16261 fprintf(debugFP, "\n");
16263 *lp = dest ? line : old_line;
16268 // [HGM] vari: routines for shelving variations
16271 PushInner(int firstMove, int lastMove)
16273 int i, j, nrMoves = lastMove - firstMove;
16275 // push current tail of game on stack
16276 savedResult[storedGames] = gameInfo.result;
16277 savedDetails[storedGames] = gameInfo.resultDetails;
16278 gameInfo.resultDetails = NULL;
16279 savedFirst[storedGames] = firstMove;
16280 savedLast [storedGames] = lastMove;
16281 savedFramePtr[storedGames] = framePtr;
16282 framePtr -= nrMoves; // reserve space for the boards
16283 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16284 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16285 for(j=0; j<MOVE_LEN; j++)
16286 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16287 for(j=0; j<2*MOVE_LEN; j++)
16288 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16289 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16290 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16291 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16292 pvInfoList[firstMove+i-1].depth = 0;
16293 commentList[framePtr+i] = commentList[firstMove+i];
16294 commentList[firstMove+i] = NULL;
16298 forwardMostMove = firstMove; // truncate game so we can start variation
16302 PushTail(int firstMove, int lastMove)
16304 if(appData.icsActive) { // only in local mode
16305 forwardMostMove = currentMove; // mimic old ICS behavior
16308 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16310 PushInner(firstMove, lastMove);
16311 if(storedGames == 1) GreyRevert(FALSE);
16315 PopInner(Boolean annotate)
16318 char buf[8000], moveBuf[20];
16321 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16322 nrMoves = savedLast[storedGames] - currentMove;
16325 if(!WhiteOnMove(currentMove))
16326 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16327 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16328 for(i=currentMove; i<forwardMostMove; i++) {
16330 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16331 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16332 strcat(buf, moveBuf);
16333 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16334 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16338 for(i=1; i<=nrMoves; i++) { // copy last variation back
16339 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16340 for(j=0; j<MOVE_LEN; j++)
16341 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16342 for(j=0; j<2*MOVE_LEN; j++)
16343 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16344 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16345 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16346 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16347 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16348 commentList[currentMove+i] = commentList[framePtr+i];
16349 commentList[framePtr+i] = NULL;
16351 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16352 framePtr = savedFramePtr[storedGames];
16353 gameInfo.result = savedResult[storedGames];
16354 if(gameInfo.resultDetails != NULL) {
16355 free(gameInfo.resultDetails);
16357 gameInfo.resultDetails = savedDetails[storedGames];
16358 forwardMostMove = currentMove + nrMoves;
16362 PopTail(Boolean annotate)
16364 if(appData.icsActive) return FALSE; // only in local mode
16365 if(!storedGames) return FALSE; // sanity
16366 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16368 PopInner(annotate);
16370 if(storedGames == 0) GreyRevert(TRUE);
16376 { // remove all shelved variations
16378 for(i=0; i<storedGames; i++) {
16379 if(savedDetails[i])
16380 free(savedDetails[i]);
16381 savedDetails[i] = NULL;
16383 for(i=framePtr; i<MAX_MOVES; i++) {
16384 if(commentList[i]) free(commentList[i]);
16385 commentList[i] = NULL;
16387 framePtr = MAX_MOVES-1;
16392 LoadVariation(int index, char *text)
16393 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16394 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16395 int level = 0, move;
16397 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16398 // first find outermost bracketing variation
16399 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16400 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16401 if(*p == '{') wait = '}'; else
16402 if(*p == '[') wait = ']'; else
16403 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16404 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16406 if(*p == wait) wait = NULLCHAR; // closing ]} found
16409 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16410 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16411 end[1] = NULLCHAR; // clip off comment beyond variation
16412 ToNrEvent(currentMove-1);
16413 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16414 // kludge: use ParsePV() to append variation to game
16415 move = currentMove;
16416 ParsePV(start, TRUE, TRUE);
16417 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16418 ClearPremoveHighlights();
16420 ToNrEvent(currentMove+1);