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 (first.twoMachinesColor[0] == 'w') {
12695 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12696 gameInfo.white, gameInfo.black,
12697 first.matchWins, second.matchWins,
12698 matchGame - 1 - (first.matchWins + second.matchWins));
12700 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12701 gameInfo.white, gameInfo.black,
12702 second.matchWins, first.matchWins,
12703 matchGame - 1 - (first.matchWins + second.matchWins));
12706 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12712 SettingsMenuIfReady()
12714 if (second.lastPing != second.lastPong) {
12715 DisplayMessage("", _("Waiting for second chess program"));
12716 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12720 DisplayMessage("", "");
12721 SettingsPopUp(&second);
12725 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12728 if (cps->pr == NULL) {
12729 StartChessProgram(cps);
12730 if (cps->protocolVersion == 1) {
12733 /* kludge: allow timeout for initial "feature" command */
12735 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12736 DisplayMessage("", buf);
12737 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12745 TwoMachinesEvent P((void))
12749 ChessProgramState *onmove;
12750 char *bookHit = NULL;
12751 static int stalling = 0;
12755 if (appData.noChessProgram) return;
12757 switch (gameMode) {
12758 case TwoMachinesPlay:
12760 case MachinePlaysWhite:
12761 case MachinePlaysBlack:
12762 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12763 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12767 case BeginningOfGame:
12768 case PlayFromGameFile:
12771 if (gameMode != EditGame) return;
12774 EditPositionDone(TRUE);
12785 // forwardMostMove = currentMove;
12786 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12788 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12790 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12791 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12792 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12796 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12797 SendToProgram("force\n", &second);
12799 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12802 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12803 if(appData.matchPause>10000 || appData.matchPause<10)
12804 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12805 wait = SubtractTimeMarks(&now, &pauseStart);
12806 if(wait < appData.matchPause) {
12807 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12811 DisplayMessage("", "");
12812 if (startedFromSetupPosition) {
12813 SendBoard(&second, backwardMostMove);
12814 if (appData.debugMode) {
12815 fprintf(debugFP, "Two Machines\n");
12818 for (i = backwardMostMove; i < forwardMostMove; i++) {
12819 SendMoveToProgram(i, &second);
12822 gameMode = TwoMachinesPlay;
12826 DisplayTwoMachinesTitle();
12828 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12833 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12834 SendToProgram(first.computerString, &first);
12835 if (first.sendName) {
12836 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12837 SendToProgram(buf, &first);
12839 SendToProgram(second.computerString, &second);
12840 if (second.sendName) {
12841 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12842 SendToProgram(buf, &second);
12846 if (!first.sendTime || !second.sendTime) {
12847 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12848 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12850 if (onmove->sendTime) {
12851 if (onmove->useColors) {
12852 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12854 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12856 if (onmove->useColors) {
12857 SendToProgram(onmove->twoMachinesColor, onmove);
12859 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12860 // SendToProgram("go\n", onmove);
12861 onmove->maybeThinking = TRUE;
12862 SetMachineThinkingEnables();
12866 if(bookHit) { // [HGM] book: simulate book reply
12867 static char bookMove[MSG_SIZ]; // a bit generous?
12869 programStats.nodes = programStats.depth = programStats.time =
12870 programStats.score = programStats.got_only_move = 0;
12871 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12873 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12874 strcat(bookMove, bookHit);
12875 savedMessage = bookMove; // args for deferred call
12876 savedState = onmove;
12877 ScheduleDelayedEvent(DeferredBookMove, 1);
12884 if (gameMode == Training) {
12885 SetTrainingModeOff();
12886 gameMode = PlayFromGameFile;
12887 DisplayMessage("", _("Training mode off"));
12889 gameMode = Training;
12890 animateTraining = appData.animate;
12892 /* make sure we are not already at the end of the game */
12893 if (currentMove < forwardMostMove) {
12894 SetTrainingModeOn();
12895 DisplayMessage("", _("Training mode on"));
12897 gameMode = PlayFromGameFile;
12898 DisplayError(_("Already at end of game"), 0);
12907 if (!appData.icsActive) return;
12908 switch (gameMode) {
12909 case IcsPlayingWhite:
12910 case IcsPlayingBlack:
12913 case BeginningOfGame:
12921 EditPositionDone(TRUE);
12934 gameMode = IcsIdle;
12945 switch (gameMode) {
12947 SetTrainingModeOff();
12949 case MachinePlaysWhite:
12950 case MachinePlaysBlack:
12951 case BeginningOfGame:
12952 SendToProgram("force\n", &first);
12953 SetUserThinkingEnables();
12955 case PlayFromGameFile:
12956 (void) StopLoadGameTimer();
12957 if (gameFileFP != NULL) {
12962 EditPositionDone(TRUE);
12967 SendToProgram("force\n", &first);
12969 case TwoMachinesPlay:
12970 GameEnds(EndOfFile, NULL, GE_PLAYER);
12971 ResurrectChessProgram();
12972 SetUserThinkingEnables();
12975 ResurrectChessProgram();
12977 case IcsPlayingBlack:
12978 case IcsPlayingWhite:
12979 DisplayError(_("Warning: You are still playing a game"), 0);
12982 DisplayError(_("Warning: You are still observing a game"), 0);
12985 DisplayError(_("Warning: You are still examining a game"), 0);
12996 first.offeredDraw = second.offeredDraw = 0;
12998 if (gameMode == PlayFromGameFile) {
12999 whiteTimeRemaining = timeRemaining[0][currentMove];
13000 blackTimeRemaining = timeRemaining[1][currentMove];
13004 if (gameMode == MachinePlaysWhite ||
13005 gameMode == MachinePlaysBlack ||
13006 gameMode == TwoMachinesPlay ||
13007 gameMode == EndOfGame) {
13008 i = forwardMostMove;
13009 while (i > currentMove) {
13010 SendToProgram("undo\n", &first);
13013 whiteTimeRemaining = timeRemaining[0][currentMove];
13014 blackTimeRemaining = timeRemaining[1][currentMove];
13015 DisplayBothClocks();
13016 if (whiteFlag || blackFlag) {
13017 whiteFlag = blackFlag = 0;
13022 gameMode = EditGame;
13029 EditPositionEvent()
13031 if (gameMode == EditPosition) {
13037 if (gameMode != EditGame) return;
13039 gameMode = EditPosition;
13042 if (currentMove > 0)
13043 CopyBoard(boards[0], boards[currentMove]);
13045 blackPlaysFirst = !WhiteOnMove(currentMove);
13047 currentMove = forwardMostMove = backwardMostMove = 0;
13048 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13055 /* [DM] icsEngineAnalyze - possible call from other functions */
13056 if (appData.icsEngineAnalyze) {
13057 appData.icsEngineAnalyze = FALSE;
13059 DisplayMessage("",_("Close ICS engine analyze..."));
13061 if (first.analysisSupport && first.analyzing) {
13062 SendToProgram("exit\n", &first);
13063 first.analyzing = FALSE;
13065 thinkOutput[0] = NULLCHAR;
13069 EditPositionDone(Boolean fakeRights)
13071 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13073 startedFromSetupPosition = TRUE;
13074 InitChessProgram(&first, FALSE);
13075 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13076 boards[0][EP_STATUS] = EP_NONE;
13077 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13078 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13079 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13080 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13081 } else boards[0][CASTLING][2] = NoRights;
13082 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13083 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13084 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13085 } else boards[0][CASTLING][5] = NoRights;
13087 SendToProgram("force\n", &first);
13088 if (blackPlaysFirst) {
13089 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13090 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13091 currentMove = forwardMostMove = backwardMostMove = 1;
13092 CopyBoard(boards[1], boards[0]);
13094 currentMove = forwardMostMove = backwardMostMove = 0;
13096 SendBoard(&first, forwardMostMove);
13097 if (appData.debugMode) {
13098 fprintf(debugFP, "EditPosDone\n");
13101 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13102 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13103 gameMode = EditGame;
13105 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13106 ClearHighlights(); /* [AS] */
13109 /* Pause for `ms' milliseconds */
13110 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13120 } while (SubtractTimeMarks(&m2, &m1) < ms);
13123 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13125 SendMultiLineToICS(buf)
13128 char temp[MSG_SIZ+1], *p;
13135 strncpy(temp, buf, len);
13140 if (*p == '\n' || *p == '\r')
13145 strcat(temp, "\n");
13147 SendToPlayer(temp, strlen(temp));
13151 SetWhiteToPlayEvent()
13153 if (gameMode == EditPosition) {
13154 blackPlaysFirst = FALSE;
13155 DisplayBothClocks(); /* works because currentMove is 0 */
13156 } else if (gameMode == IcsExamining) {
13157 SendToICS(ics_prefix);
13158 SendToICS("tomove white\n");
13163 SetBlackToPlayEvent()
13165 if (gameMode == EditPosition) {
13166 blackPlaysFirst = TRUE;
13167 currentMove = 1; /* kludge */
13168 DisplayBothClocks();
13170 } else if (gameMode == IcsExamining) {
13171 SendToICS(ics_prefix);
13172 SendToICS("tomove black\n");
13177 EditPositionMenuEvent(selection, x, y)
13178 ChessSquare selection;
13182 ChessSquare piece = boards[0][y][x];
13184 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13186 switch (selection) {
13188 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13189 SendToICS(ics_prefix);
13190 SendToICS("bsetup clear\n");
13191 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13192 SendToICS(ics_prefix);
13193 SendToICS("clearboard\n");
13195 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13196 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13197 for (y = 0; y < BOARD_HEIGHT; y++) {
13198 if (gameMode == IcsExamining) {
13199 if (boards[currentMove][y][x] != EmptySquare) {
13200 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13205 boards[0][y][x] = p;
13210 if (gameMode == EditPosition) {
13211 DrawPosition(FALSE, boards[0]);
13216 SetWhiteToPlayEvent();
13220 SetBlackToPlayEvent();
13224 if (gameMode == IcsExamining) {
13225 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13226 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13229 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13230 if(x == BOARD_LEFT-2) {
13231 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13232 boards[0][y][1] = 0;
13234 if(x == BOARD_RGHT+1) {
13235 if(y >= gameInfo.holdingsSize) break;
13236 boards[0][y][BOARD_WIDTH-2] = 0;
13239 boards[0][y][x] = EmptySquare;
13240 DrawPosition(FALSE, boards[0]);
13245 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13246 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13247 selection = (ChessSquare) (PROMOTED piece);
13248 } else if(piece == EmptySquare) selection = WhiteSilver;
13249 else selection = (ChessSquare)((int)piece - 1);
13253 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13254 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13255 selection = (ChessSquare) (DEMOTED piece);
13256 } else if(piece == EmptySquare) selection = BlackSilver;
13257 else selection = (ChessSquare)((int)piece + 1);
13262 if(gameInfo.variant == VariantShatranj ||
13263 gameInfo.variant == VariantXiangqi ||
13264 gameInfo.variant == VariantCourier ||
13265 gameInfo.variant == VariantMakruk )
13266 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13271 if(gameInfo.variant == VariantXiangqi)
13272 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13273 if(gameInfo.variant == VariantKnightmate)
13274 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13277 if (gameMode == IcsExamining) {
13278 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13279 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13280 PieceToChar(selection), AAA + x, ONE + y);
13283 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13285 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13286 n = PieceToNumber(selection - BlackPawn);
13287 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13288 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13289 boards[0][BOARD_HEIGHT-1-n][1]++;
13291 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13292 n = PieceToNumber(selection);
13293 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13294 boards[0][n][BOARD_WIDTH-1] = selection;
13295 boards[0][n][BOARD_WIDTH-2]++;
13298 boards[0][y][x] = selection;
13299 DrawPosition(TRUE, boards[0]);
13307 DropMenuEvent(selection, x, y)
13308 ChessSquare selection;
13311 ChessMove moveType;
13313 switch (gameMode) {
13314 case IcsPlayingWhite:
13315 case MachinePlaysBlack:
13316 if (!WhiteOnMove(currentMove)) {
13317 DisplayMoveError(_("It is Black's turn"));
13320 moveType = WhiteDrop;
13322 case IcsPlayingBlack:
13323 case MachinePlaysWhite:
13324 if (WhiteOnMove(currentMove)) {
13325 DisplayMoveError(_("It is White's turn"));
13328 moveType = BlackDrop;
13331 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13337 if (moveType == BlackDrop && selection < BlackPawn) {
13338 selection = (ChessSquare) ((int) selection
13339 + (int) BlackPawn - (int) WhitePawn);
13341 if (boards[currentMove][y][x] != EmptySquare) {
13342 DisplayMoveError(_("That square is occupied"));
13346 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13352 /* Accept a pending offer of any kind from opponent */
13354 if (appData.icsActive) {
13355 SendToICS(ics_prefix);
13356 SendToICS("accept\n");
13357 } else if (cmailMsgLoaded) {
13358 if (currentMove == cmailOldMove &&
13359 commentList[cmailOldMove] != NULL &&
13360 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13361 "Black offers a draw" : "White offers a draw")) {
13363 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13364 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13366 DisplayError(_("There is no pending offer on this move"), 0);
13367 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13370 /* Not used for offers from chess program */
13377 /* Decline a pending offer of any kind from opponent */
13379 if (appData.icsActive) {
13380 SendToICS(ics_prefix);
13381 SendToICS("decline\n");
13382 } else if (cmailMsgLoaded) {
13383 if (currentMove == cmailOldMove &&
13384 commentList[cmailOldMove] != NULL &&
13385 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13386 "Black offers a draw" : "White offers a draw")) {
13388 AppendComment(cmailOldMove, "Draw declined", TRUE);
13389 DisplayComment(cmailOldMove - 1, "Draw declined");
13392 DisplayError(_("There is no pending offer on this move"), 0);
13395 /* Not used for offers from chess program */
13402 /* Issue ICS rematch command */
13403 if (appData.icsActive) {
13404 SendToICS(ics_prefix);
13405 SendToICS("rematch\n");
13412 /* Call your opponent's flag (claim a win on time) */
13413 if (appData.icsActive) {
13414 SendToICS(ics_prefix);
13415 SendToICS("flag\n");
13417 switch (gameMode) {
13420 case MachinePlaysWhite:
13423 GameEnds(GameIsDrawn, "Both players ran out of time",
13426 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13428 DisplayError(_("Your opponent is not out of time"), 0);
13431 case MachinePlaysBlack:
13434 GameEnds(GameIsDrawn, "Both players ran out of time",
13437 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13439 DisplayError(_("Your opponent is not out of time"), 0);
13447 ClockClick(int which)
13448 { // [HGM] code moved to back-end from winboard.c
13449 if(which) { // black clock
13450 if (gameMode == EditPosition || gameMode == IcsExamining) {
13451 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13452 SetBlackToPlayEvent();
13453 } else if (gameMode == EditGame || shiftKey) {
13454 AdjustClock(which, -1);
13455 } else if (gameMode == IcsPlayingWhite ||
13456 gameMode == MachinePlaysBlack) {
13459 } else { // white clock
13460 if (gameMode == EditPosition || gameMode == IcsExamining) {
13461 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13462 SetWhiteToPlayEvent();
13463 } else if (gameMode == EditGame || shiftKey) {
13464 AdjustClock(which, -1);
13465 } else if (gameMode == IcsPlayingBlack ||
13466 gameMode == MachinePlaysWhite) {
13475 /* Offer draw or accept pending draw offer from opponent */
13477 if (appData.icsActive) {
13478 /* Note: tournament rules require draw offers to be
13479 made after you make your move but before you punch
13480 your clock. Currently ICS doesn't let you do that;
13481 instead, you immediately punch your clock after making
13482 a move, but you can offer a draw at any time. */
13484 SendToICS(ics_prefix);
13485 SendToICS("draw\n");
13486 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13487 } else if (cmailMsgLoaded) {
13488 if (currentMove == cmailOldMove &&
13489 commentList[cmailOldMove] != NULL &&
13490 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13491 "Black offers a draw" : "White offers a draw")) {
13492 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13493 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13494 } else if (currentMove == cmailOldMove + 1) {
13495 char *offer = WhiteOnMove(cmailOldMove) ?
13496 "White offers a draw" : "Black offers a draw";
13497 AppendComment(currentMove, offer, TRUE);
13498 DisplayComment(currentMove - 1, offer);
13499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13501 DisplayError(_("You must make your move before offering a draw"), 0);
13502 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13504 } else if (first.offeredDraw) {
13505 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13507 if (first.sendDrawOffers) {
13508 SendToProgram("draw\n", &first);
13509 userOfferedDraw = TRUE;
13517 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13519 if (appData.icsActive) {
13520 SendToICS(ics_prefix);
13521 SendToICS("adjourn\n");
13523 /* Currently GNU Chess doesn't offer or accept Adjourns */
13531 /* Offer Abort or accept pending Abort offer from opponent */
13533 if (appData.icsActive) {
13534 SendToICS(ics_prefix);
13535 SendToICS("abort\n");
13537 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13544 /* Resign. You can do this even if it's not your turn. */
13546 if (appData.icsActive) {
13547 SendToICS(ics_prefix);
13548 SendToICS("resign\n");
13550 switch (gameMode) {
13551 case MachinePlaysWhite:
13552 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13554 case MachinePlaysBlack:
13555 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13558 if (cmailMsgLoaded) {
13560 if (WhiteOnMove(cmailOldMove)) {
13561 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13563 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13565 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13576 StopObservingEvent()
13578 /* Stop observing current games */
13579 SendToICS(ics_prefix);
13580 SendToICS("unobserve\n");
13584 StopExaminingEvent()
13586 /* Stop observing current game */
13587 SendToICS(ics_prefix);
13588 SendToICS("unexamine\n");
13592 ForwardInner(target)
13597 if (appData.debugMode)
13598 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13599 target, currentMove, forwardMostMove);
13601 if (gameMode == EditPosition)
13604 if (gameMode == PlayFromGameFile && !pausing)
13607 if (gameMode == IcsExamining && pausing)
13608 limit = pauseExamForwardMostMove;
13610 limit = forwardMostMove;
13612 if (target > limit) target = limit;
13614 if (target > 0 && moveList[target - 1][0]) {
13615 int fromX, fromY, toX, toY;
13616 toX = moveList[target - 1][2] - AAA;
13617 toY = moveList[target - 1][3] - ONE;
13618 if (moveList[target - 1][1] == '@') {
13619 if (appData.highlightLastMove) {
13620 SetHighlights(-1, -1, toX, toY);
13623 fromX = moveList[target - 1][0] - AAA;
13624 fromY = moveList[target - 1][1] - ONE;
13625 if (target == currentMove + 1) {
13626 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13628 if (appData.highlightLastMove) {
13629 SetHighlights(fromX, fromY, toX, toY);
13633 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13634 gameMode == Training || gameMode == PlayFromGameFile ||
13635 gameMode == AnalyzeFile) {
13636 while (currentMove < target) {
13637 SendMoveToProgram(currentMove++, &first);
13640 currentMove = target;
13643 if (gameMode == EditGame || gameMode == EndOfGame) {
13644 whiteTimeRemaining = timeRemaining[0][currentMove];
13645 blackTimeRemaining = timeRemaining[1][currentMove];
13647 DisplayBothClocks();
13648 DisplayMove(currentMove - 1);
13649 DrawPosition(FALSE, boards[currentMove]);
13650 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13651 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13652 DisplayComment(currentMove - 1, commentList[currentMove]);
13654 DisplayBook(currentMove);
13661 if (gameMode == IcsExamining && !pausing) {
13662 SendToICS(ics_prefix);
13663 SendToICS("forward\n");
13665 ForwardInner(currentMove + 1);
13672 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13673 /* to optimze, we temporarily turn off analysis mode while we feed
13674 * the remaining moves to the engine. Otherwise we get analysis output
13677 if (first.analysisSupport) {
13678 SendToProgram("exit\nforce\n", &first);
13679 first.analyzing = FALSE;
13683 if (gameMode == IcsExamining && !pausing) {
13684 SendToICS(ics_prefix);
13685 SendToICS("forward 999999\n");
13687 ForwardInner(forwardMostMove);
13690 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13691 /* we have fed all the moves, so reactivate analysis mode */
13692 SendToProgram("analyze\n", &first);
13693 first.analyzing = TRUE;
13694 /*first.maybeThinking = TRUE;*/
13695 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13700 BackwardInner(target)
13703 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13705 if (appData.debugMode)
13706 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13707 target, currentMove, forwardMostMove);
13709 if (gameMode == EditPosition) return;
13710 if (currentMove <= backwardMostMove) {
13712 DrawPosition(full_redraw, boards[currentMove]);
13715 if (gameMode == PlayFromGameFile && !pausing)
13718 if (moveList[target][0]) {
13719 int fromX, fromY, toX, toY;
13720 toX = moveList[target][2] - AAA;
13721 toY = moveList[target][3] - ONE;
13722 if (moveList[target][1] == '@') {
13723 if (appData.highlightLastMove) {
13724 SetHighlights(-1, -1, toX, toY);
13727 fromX = moveList[target][0] - AAA;
13728 fromY = moveList[target][1] - ONE;
13729 if (target == currentMove - 1) {
13730 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13732 if (appData.highlightLastMove) {
13733 SetHighlights(fromX, fromY, toX, toY);
13737 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13738 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13739 while (currentMove > target) {
13740 SendToProgram("undo\n", &first);
13744 currentMove = target;
13747 if (gameMode == EditGame || gameMode == EndOfGame) {
13748 whiteTimeRemaining = timeRemaining[0][currentMove];
13749 blackTimeRemaining = timeRemaining[1][currentMove];
13751 DisplayBothClocks();
13752 DisplayMove(currentMove - 1);
13753 DrawPosition(full_redraw, boards[currentMove]);
13754 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13755 // [HGM] PV info: routine tests if comment empty
13756 DisplayComment(currentMove - 1, commentList[currentMove]);
13757 DisplayBook(currentMove);
13763 if (gameMode == IcsExamining && !pausing) {
13764 SendToICS(ics_prefix);
13765 SendToICS("backward\n");
13767 BackwardInner(currentMove - 1);
13774 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13775 /* to optimize, we temporarily turn off analysis mode while we undo
13776 * all the moves. Otherwise we get analysis output after each undo.
13778 if (first.analysisSupport) {
13779 SendToProgram("exit\nforce\n", &first);
13780 first.analyzing = FALSE;
13784 if (gameMode == IcsExamining && !pausing) {
13785 SendToICS(ics_prefix);
13786 SendToICS("backward 999999\n");
13788 BackwardInner(backwardMostMove);
13791 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13792 /* we have fed all the moves, so reactivate analysis mode */
13793 SendToProgram("analyze\n", &first);
13794 first.analyzing = TRUE;
13795 /*first.maybeThinking = TRUE;*/
13796 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13803 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13804 if (to >= forwardMostMove) to = forwardMostMove;
13805 if (to <= backwardMostMove) to = backwardMostMove;
13806 if (to < currentMove) {
13814 RevertEvent(Boolean annotate)
13816 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13819 if (gameMode != IcsExamining) {
13820 DisplayError(_("You are not examining a game"), 0);
13824 DisplayError(_("You can't revert while pausing"), 0);
13827 SendToICS(ics_prefix);
13828 SendToICS("revert\n");
13834 switch (gameMode) {
13835 case MachinePlaysWhite:
13836 case MachinePlaysBlack:
13837 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13838 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13841 if (forwardMostMove < 2) return;
13842 currentMove = forwardMostMove = forwardMostMove - 2;
13843 whiteTimeRemaining = timeRemaining[0][currentMove];
13844 blackTimeRemaining = timeRemaining[1][currentMove];
13845 DisplayBothClocks();
13846 DisplayMove(currentMove - 1);
13847 ClearHighlights();/*!! could figure this out*/
13848 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13849 SendToProgram("remove\n", &first);
13850 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13853 case BeginningOfGame:
13857 case IcsPlayingWhite:
13858 case IcsPlayingBlack:
13859 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13860 SendToICS(ics_prefix);
13861 SendToICS("takeback 2\n");
13863 SendToICS(ics_prefix);
13864 SendToICS("takeback 1\n");
13873 ChessProgramState *cps;
13875 switch (gameMode) {
13876 case MachinePlaysWhite:
13877 if (!WhiteOnMove(forwardMostMove)) {
13878 DisplayError(_("It is your turn"), 0);
13883 case MachinePlaysBlack:
13884 if (WhiteOnMove(forwardMostMove)) {
13885 DisplayError(_("It is your turn"), 0);
13890 case TwoMachinesPlay:
13891 if (WhiteOnMove(forwardMostMove) ==
13892 (first.twoMachinesColor[0] == 'w')) {
13898 case BeginningOfGame:
13902 SendToProgram("?\n", cps);
13906 TruncateGameEvent()
13909 if (gameMode != EditGame) return;
13916 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13917 if (forwardMostMove > currentMove) {
13918 if (gameInfo.resultDetails != NULL) {
13919 free(gameInfo.resultDetails);
13920 gameInfo.resultDetails = NULL;
13921 gameInfo.result = GameUnfinished;
13923 forwardMostMove = currentMove;
13924 HistorySet(parseList, backwardMostMove, forwardMostMove,
13932 if (appData.noChessProgram) return;
13933 switch (gameMode) {
13934 case MachinePlaysWhite:
13935 if (WhiteOnMove(forwardMostMove)) {
13936 DisplayError(_("Wait until your turn"), 0);
13940 case BeginningOfGame:
13941 case MachinePlaysBlack:
13942 if (!WhiteOnMove(forwardMostMove)) {
13943 DisplayError(_("Wait until your turn"), 0);
13948 DisplayError(_("No hint available"), 0);
13951 SendToProgram("hint\n", &first);
13952 hintRequested = TRUE;
13958 if (appData.noChessProgram) return;
13959 switch (gameMode) {
13960 case MachinePlaysWhite:
13961 if (WhiteOnMove(forwardMostMove)) {
13962 DisplayError(_("Wait until your turn"), 0);
13966 case BeginningOfGame:
13967 case MachinePlaysBlack:
13968 if (!WhiteOnMove(forwardMostMove)) {
13969 DisplayError(_("Wait until your turn"), 0);
13974 EditPositionDone(TRUE);
13976 case TwoMachinesPlay:
13981 SendToProgram("bk\n", &first);
13982 bookOutput[0] = NULLCHAR;
13983 bookRequested = TRUE;
13989 char *tags = PGNTags(&gameInfo);
13990 TagsPopUp(tags, CmailMsg());
13994 /* end button procedures */
13997 PrintPosition(fp, move)
14003 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14004 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14005 char c = PieceToChar(boards[move][i][j]);
14006 fputc(c == 'x' ? '.' : c, fp);
14007 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14010 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14011 fprintf(fp, "white to play\n");
14013 fprintf(fp, "black to play\n");
14020 if (gameInfo.white != NULL) {
14021 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14027 /* Find last component of program's own name, using some heuristics */
14029 TidyProgramName(prog, host, buf)
14030 char *prog, *host, buf[MSG_SIZ];
14033 int local = (strcmp(host, "localhost") == 0);
14034 while (!local && (p = strchr(prog, ';')) != NULL) {
14036 while (*p == ' ') p++;
14039 if (*prog == '"' || *prog == '\'') {
14040 q = strchr(prog + 1, *prog);
14042 q = strchr(prog, ' ');
14044 if (q == NULL) q = prog + strlen(prog);
14046 while (p >= prog && *p != '/' && *p != '\\') p--;
14048 if(p == prog && *p == '"') p++;
14049 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14050 memcpy(buf, p, q - p);
14051 buf[q - p] = NULLCHAR;
14059 TimeControlTagValue()
14062 if (!appData.clockMode) {
14063 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14064 } else if (movesPerSession > 0) {
14065 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14066 } else if (timeIncrement == 0) {
14067 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14069 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14071 return StrSave(buf);
14077 /* This routine is used only for certain modes */
14078 VariantClass v = gameInfo.variant;
14079 ChessMove r = GameUnfinished;
14082 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14083 r = gameInfo.result;
14084 p = gameInfo.resultDetails;
14085 gameInfo.resultDetails = NULL;
14087 ClearGameInfo(&gameInfo);
14088 gameInfo.variant = v;
14090 switch (gameMode) {
14091 case MachinePlaysWhite:
14092 gameInfo.event = StrSave( appData.pgnEventHeader );
14093 gameInfo.site = StrSave(HostName());
14094 gameInfo.date = PGNDate();
14095 gameInfo.round = StrSave("-");
14096 gameInfo.white = StrSave(first.tidy);
14097 gameInfo.black = StrSave(UserName());
14098 gameInfo.timeControl = TimeControlTagValue();
14101 case MachinePlaysBlack:
14102 gameInfo.event = StrSave( appData.pgnEventHeader );
14103 gameInfo.site = StrSave(HostName());
14104 gameInfo.date = PGNDate();
14105 gameInfo.round = StrSave("-");
14106 gameInfo.white = StrSave(UserName());
14107 gameInfo.black = StrSave(first.tidy);
14108 gameInfo.timeControl = TimeControlTagValue();
14111 case TwoMachinesPlay:
14112 gameInfo.event = StrSave( appData.pgnEventHeader );
14113 gameInfo.site = StrSave(HostName());
14114 gameInfo.date = PGNDate();
14117 snprintf(buf, MSG_SIZ, "%d", roundNr);
14118 gameInfo.round = StrSave(buf);
14120 gameInfo.round = StrSave("-");
14122 if (first.twoMachinesColor[0] == 'w') {
14123 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14124 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14126 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14127 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14129 gameInfo.timeControl = TimeControlTagValue();
14133 gameInfo.event = StrSave("Edited game");
14134 gameInfo.site = StrSave(HostName());
14135 gameInfo.date = PGNDate();
14136 gameInfo.round = StrSave("-");
14137 gameInfo.white = StrSave("-");
14138 gameInfo.black = StrSave("-");
14139 gameInfo.result = r;
14140 gameInfo.resultDetails = p;
14144 gameInfo.event = StrSave("Edited position");
14145 gameInfo.site = StrSave(HostName());
14146 gameInfo.date = PGNDate();
14147 gameInfo.round = StrSave("-");
14148 gameInfo.white = StrSave("-");
14149 gameInfo.black = StrSave("-");
14152 case IcsPlayingWhite:
14153 case IcsPlayingBlack:
14158 case PlayFromGameFile:
14159 gameInfo.event = StrSave("Game from non-PGN file");
14160 gameInfo.site = StrSave(HostName());
14161 gameInfo.date = PGNDate();
14162 gameInfo.round = StrSave("-");
14163 gameInfo.white = StrSave("?");
14164 gameInfo.black = StrSave("?");
14173 ReplaceComment(index, text)
14181 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14182 pvInfoList[index-1].depth == len &&
14183 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14184 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14185 while (*text == '\n') text++;
14186 len = strlen(text);
14187 while (len > 0 && text[len - 1] == '\n') len--;
14189 if (commentList[index] != NULL)
14190 free(commentList[index]);
14193 commentList[index] = NULL;
14196 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14197 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14198 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14199 commentList[index] = (char *) malloc(len + 2);
14200 strncpy(commentList[index], text, len);
14201 commentList[index][len] = '\n';
14202 commentList[index][len + 1] = NULLCHAR;
14204 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14206 commentList[index] = (char *) malloc(len + 7);
14207 safeStrCpy(commentList[index], "{\n", 3);
14208 safeStrCpy(commentList[index]+2, text, len+1);
14209 commentList[index][len+2] = NULLCHAR;
14210 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14211 strcat(commentList[index], "\n}\n");
14225 if (ch == '\r') continue;
14227 } while (ch != '\0');
14231 AppendComment(index, text, addBraces)
14234 Boolean addBraces; // [HGM] braces: tells if we should add {}
14239 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14240 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14243 while (*text == '\n') text++;
14244 len = strlen(text);
14245 while (len > 0 && text[len - 1] == '\n') len--;
14247 if (len == 0) return;
14249 if (commentList[index] != NULL) {
14250 old = commentList[index];
14251 oldlen = strlen(old);
14252 while(commentList[index][oldlen-1] == '\n')
14253 commentList[index][--oldlen] = NULLCHAR;
14254 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14255 safeStrCpy(commentList[index], old, oldlen + len + 6);
14257 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14258 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14259 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14260 while (*text == '\n') { text++; len--; }
14261 commentList[index][--oldlen] = NULLCHAR;
14263 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14264 else strcat(commentList[index], "\n");
14265 strcat(commentList[index], text);
14266 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14267 else strcat(commentList[index], "\n");
14269 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14271 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14272 else commentList[index][0] = NULLCHAR;
14273 strcat(commentList[index], text);
14274 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14275 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14279 static char * FindStr( char * text, char * sub_text )
14281 char * result = strstr( text, sub_text );
14283 if( result != NULL ) {
14284 result += strlen( sub_text );
14290 /* [AS] Try to extract PV info from PGN comment */
14291 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14292 char *GetInfoFromComment( int index, char * text )
14294 char * sep = text, *p;
14296 if( text != NULL && index > 0 ) {
14299 int time = -1, sec = 0, deci;
14300 char * s_eval = FindStr( text, "[%eval " );
14301 char * s_emt = FindStr( text, "[%emt " );
14303 if( s_eval != NULL || s_emt != NULL ) {
14307 if( s_eval != NULL ) {
14308 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14312 if( delim != ']' ) {
14317 if( s_emt != NULL ) {
14322 /* We expect something like: [+|-]nnn.nn/dd */
14325 if(*text != '{') return text; // [HGM] braces: must be normal comment
14327 sep = strchr( text, '/' );
14328 if( sep == NULL || sep < (text+4) ) {
14333 if(p[1] == '(') { // comment starts with PV
14334 p = strchr(p, ')'); // locate end of PV
14335 if(p == NULL || sep < p+5) return text;
14336 // at this point we have something like "{(.*) +0.23/6 ..."
14337 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14338 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14339 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14341 time = -1; sec = -1; deci = -1;
14342 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14343 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14344 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14345 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14349 if( score_lo < 0 || score_lo >= 100 ) {
14353 if(sec >= 0) time = 600*time + 10*sec; else
14354 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14356 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14358 /* [HGM] PV time: now locate end of PV info */
14359 while( *++sep >= '0' && *sep <= '9'); // strip depth
14361 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14363 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14365 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14366 while(*sep == ' ') sep++;
14377 pvInfoList[index-1].depth = depth;
14378 pvInfoList[index-1].score = score;
14379 pvInfoList[index-1].time = 10*time; // centi-sec
14380 if(*sep == '}') *sep = 0; else *--sep = '{';
14381 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14387 SendToProgram(message, cps)
14389 ChessProgramState *cps;
14391 int count, outCount, error;
14394 if (cps->pr == NULL) return;
14397 if (appData.debugMode) {
14400 fprintf(debugFP, "%ld >%-6s: %s",
14401 SubtractTimeMarks(&now, &programStartTime),
14402 cps->which, message);
14405 count = strlen(message);
14406 outCount = OutputToProcess(cps->pr, message, count, &error);
14407 if (outCount < count && !exiting
14408 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14409 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14410 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14411 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14412 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14413 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14414 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14415 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14417 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14418 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14419 gameInfo.result = res;
14421 gameInfo.resultDetails = StrSave(buf);
14423 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14424 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14429 ReceiveFromProgram(isr, closure, message, count, error)
14430 InputSourceRef isr;
14438 ChessProgramState *cps = (ChessProgramState *)closure;
14440 if (isr != cps->isr) return; /* Killed intentionally */
14443 RemoveInputSource(cps->isr);
14444 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14445 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14446 _(cps->which), cps->program);
14447 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14448 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14449 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14450 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14451 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14453 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14454 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14455 gameInfo.result = res;
14457 gameInfo.resultDetails = StrSave(buf);
14459 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14460 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14462 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14463 _(cps->which), cps->program);
14464 RemoveInputSource(cps->isr);
14466 /* [AS] Program is misbehaving badly... kill it */
14467 if( count == -2 ) {
14468 DestroyChildProcess( cps->pr, 9 );
14472 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14477 if ((end_str = strchr(message, '\r')) != NULL)
14478 *end_str = NULLCHAR;
14479 if ((end_str = strchr(message, '\n')) != NULL)
14480 *end_str = NULLCHAR;
14482 if (appData.debugMode) {
14483 TimeMark now; int print = 1;
14484 char *quote = ""; char c; int i;
14486 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14487 char start = message[0];
14488 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14489 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14490 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14491 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14492 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14493 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14494 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14495 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14496 sscanf(message, "hint: %c", &c)!=1 &&
14497 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14498 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14499 print = (appData.engineComments >= 2);
14501 message[0] = start; // restore original message
14505 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14506 SubtractTimeMarks(&now, &programStartTime), cps->which,
14512 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14513 if (appData.icsEngineAnalyze) {
14514 if (strstr(message, "whisper") != NULL ||
14515 strstr(message, "kibitz") != NULL ||
14516 strstr(message, "tellics") != NULL) return;
14519 HandleMachineMove(message, cps);
14524 SendTimeControl(cps, mps, tc, inc, sd, st)
14525 ChessProgramState *cps;
14526 int mps, inc, sd, st;
14532 if( timeControl_2 > 0 ) {
14533 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14534 tc = timeControl_2;
14537 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14538 inc /= cps->timeOdds;
14539 st /= cps->timeOdds;
14541 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14544 /* Set exact time per move, normally using st command */
14545 if (cps->stKludge) {
14546 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14548 if (seconds == 0) {
14549 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14551 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14554 snprintf(buf, MSG_SIZ, "st %d\n", st);
14557 /* Set conventional or incremental time control, using level command */
14558 if (seconds == 0) {
14559 /* Note old gnuchess bug -- minutes:seconds used to not work.
14560 Fixed in later versions, but still avoid :seconds
14561 when seconds is 0. */
14562 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14564 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14565 seconds, inc/1000.);
14568 SendToProgram(buf, cps);
14570 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14571 /* Orthogonally, limit search to given depth */
14573 if (cps->sdKludge) {
14574 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14576 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14578 SendToProgram(buf, cps);
14581 if(cps->nps >= 0) { /* [HGM] nps */
14582 if(cps->supportsNPS == FALSE)
14583 cps->nps = -1; // don't use if engine explicitly says not supported!
14585 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14586 SendToProgram(buf, cps);
14591 ChessProgramState *WhitePlayer()
14592 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14594 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14595 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14601 SendTimeRemaining(cps, machineWhite)
14602 ChessProgramState *cps;
14603 int /*boolean*/ machineWhite;
14605 char message[MSG_SIZ];
14608 /* Note: this routine must be called when the clocks are stopped
14609 or when they have *just* been set or switched; otherwise
14610 it will be off by the time since the current tick started.
14612 if (machineWhite) {
14613 time = whiteTimeRemaining / 10;
14614 otime = blackTimeRemaining / 10;
14616 time = blackTimeRemaining / 10;
14617 otime = whiteTimeRemaining / 10;
14619 /* [HGM] translate opponent's time by time-odds factor */
14620 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14621 if (appData.debugMode) {
14622 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14625 if (time <= 0) time = 1;
14626 if (otime <= 0) otime = 1;
14628 snprintf(message, MSG_SIZ, "time %ld\n", time);
14629 SendToProgram(message, cps);
14631 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14632 SendToProgram(message, cps);
14636 BoolFeature(p, name, loc, cps)
14640 ChessProgramState *cps;
14643 int len = strlen(name);
14646 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14648 sscanf(*p, "%d", &val);
14650 while (**p && **p != ' ')
14652 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14653 SendToProgram(buf, cps);
14660 IntFeature(p, name, loc, cps)
14664 ChessProgramState *cps;
14667 int len = strlen(name);
14668 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14670 sscanf(*p, "%d", loc);
14671 while (**p && **p != ' ') (*p)++;
14672 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14673 SendToProgram(buf, cps);
14680 StringFeature(p, name, loc, cps)
14684 ChessProgramState *cps;
14687 int len = strlen(name);
14688 if (strncmp((*p), name, len) == 0
14689 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14691 sscanf(*p, "%[^\"]", loc);
14692 while (**p && **p != '\"') (*p)++;
14693 if (**p == '\"') (*p)++;
14694 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14695 SendToProgram(buf, cps);
14702 ParseOption(Option *opt, ChessProgramState *cps)
14703 // [HGM] options: process the string that defines an engine option, and determine
14704 // name, type, default value, and allowed value range
14706 char *p, *q, buf[MSG_SIZ];
14707 int n, min = (-1)<<31, max = 1<<31, def;
14709 if(p = strstr(opt->name, " -spin ")) {
14710 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14711 if(max < min) max = min; // enforce consistency
14712 if(def < min) def = min;
14713 if(def > max) def = max;
14718 } else if((p = strstr(opt->name, " -slider "))) {
14719 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14720 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14721 if(max < min) max = min; // enforce consistency
14722 if(def < min) def = min;
14723 if(def > max) def = max;
14727 opt->type = Spin; // Slider;
14728 } else if((p = strstr(opt->name, " -string "))) {
14729 opt->textValue = p+9;
14730 opt->type = TextBox;
14731 } else if((p = strstr(opt->name, " -file "))) {
14732 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14733 opt->textValue = p+7;
14734 opt->type = FileName; // FileName;
14735 } else if((p = strstr(opt->name, " -path "))) {
14736 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14737 opt->textValue = p+7;
14738 opt->type = PathName; // PathName;
14739 } else if(p = strstr(opt->name, " -check ")) {
14740 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14741 opt->value = (def != 0);
14742 opt->type = CheckBox;
14743 } else if(p = strstr(opt->name, " -combo ")) {
14744 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14745 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14746 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14747 opt->value = n = 0;
14748 while(q = StrStr(q, " /// ")) {
14749 n++; *q = 0; // count choices, and null-terminate each of them
14751 if(*q == '*') { // remember default, which is marked with * prefix
14755 cps->comboList[cps->comboCnt++] = q;
14757 cps->comboList[cps->comboCnt++] = NULL;
14759 opt->type = ComboBox;
14760 } else if(p = strstr(opt->name, " -button")) {
14761 opt->type = Button;
14762 } else if(p = strstr(opt->name, " -save")) {
14763 opt->type = SaveButton;
14764 } else return FALSE;
14765 *p = 0; // terminate option name
14766 // now look if the command-line options define a setting for this engine option.
14767 if(cps->optionSettings && cps->optionSettings[0])
14768 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14769 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14770 snprintf(buf, MSG_SIZ, "option %s", p);
14771 if(p = strstr(buf, ",")) *p = 0;
14772 if(q = strchr(buf, '=')) switch(opt->type) {
14774 for(n=0; n<opt->max; n++)
14775 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14778 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14782 opt->value = atoi(q+1);
14787 SendToProgram(buf, cps);
14793 FeatureDone(cps, val)
14794 ChessProgramState* cps;
14797 DelayedEventCallback cb = GetDelayedEvent();
14798 if ((cb == InitBackEnd3 && cps == &first) ||
14799 (cb == SettingsMenuIfReady && cps == &second) ||
14800 (cb == LoadEngine) ||
14801 (cb == TwoMachinesEventIfReady)) {
14802 CancelDelayedEvent();
14803 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14805 cps->initDone = val;
14808 /* Parse feature command from engine */
14810 ParseFeatures(args, cps)
14812 ChessProgramState *cps;
14820 while (*p == ' ') p++;
14821 if (*p == NULLCHAR) return;
14823 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14824 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14825 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14826 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14827 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14828 if (BoolFeature(&p, "reuse", &val, cps)) {
14829 /* Engine can disable reuse, but can't enable it if user said no */
14830 if (!val) cps->reuse = FALSE;
14833 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14834 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14835 if (gameMode == TwoMachinesPlay) {
14836 DisplayTwoMachinesTitle();
14842 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14843 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14844 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14845 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14846 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14847 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14848 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14849 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14850 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14851 if (IntFeature(&p, "done", &val, cps)) {
14852 FeatureDone(cps, val);
14855 /* Added by Tord: */
14856 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14857 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14858 /* End of additions by Tord */
14860 /* [HGM] added features: */
14861 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14862 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14863 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14864 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14865 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14866 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14867 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14868 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14869 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14870 SendToProgram(buf, cps);
14873 if(cps->nrOptions >= MAX_OPTIONS) {
14875 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14876 DisplayError(buf, 0);
14880 /* End of additions by HGM */
14882 /* unknown feature: complain and skip */
14884 while (*q && *q != '=') q++;
14885 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14886 SendToProgram(buf, cps);
14892 while (*p && *p != '\"') p++;
14893 if (*p == '\"') p++;
14895 while (*p && *p != ' ') p++;
14903 PeriodicUpdatesEvent(newState)
14906 if (newState == appData.periodicUpdates)
14909 appData.periodicUpdates=newState;
14911 /* Display type changes, so update it now */
14912 // DisplayAnalysis();
14914 /* Get the ball rolling again... */
14916 AnalysisPeriodicEvent(1);
14917 StartAnalysisClock();
14922 PonderNextMoveEvent(newState)
14925 if (newState == appData.ponderNextMove) return;
14926 if (gameMode == EditPosition) EditPositionDone(TRUE);
14928 SendToProgram("hard\n", &first);
14929 if (gameMode == TwoMachinesPlay) {
14930 SendToProgram("hard\n", &second);
14933 SendToProgram("easy\n", &first);
14934 thinkOutput[0] = NULLCHAR;
14935 if (gameMode == TwoMachinesPlay) {
14936 SendToProgram("easy\n", &second);
14939 appData.ponderNextMove = newState;
14943 NewSettingEvent(option, feature, command, value)
14945 int option, value, *feature;
14949 if (gameMode == EditPosition) EditPositionDone(TRUE);
14950 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14951 if(feature == NULL || *feature) SendToProgram(buf, &first);
14952 if (gameMode == TwoMachinesPlay) {
14953 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14958 ShowThinkingEvent()
14959 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14961 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14962 int newState = appData.showThinking
14963 // [HGM] thinking: other features now need thinking output as well
14964 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14966 if (oldState == newState) return;
14967 oldState = newState;
14968 if (gameMode == EditPosition) EditPositionDone(TRUE);
14970 SendToProgram("post\n", &first);
14971 if (gameMode == TwoMachinesPlay) {
14972 SendToProgram("post\n", &second);
14975 SendToProgram("nopost\n", &first);
14976 thinkOutput[0] = NULLCHAR;
14977 if (gameMode == TwoMachinesPlay) {
14978 SendToProgram("nopost\n", &second);
14981 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14985 AskQuestionEvent(title, question, replyPrefix, which)
14986 char *title; char *question; char *replyPrefix; char *which;
14988 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14989 if (pr == NoProc) return;
14990 AskQuestion(title, question, replyPrefix, pr);
14994 TypeInEvent(char firstChar)
14996 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14997 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14998 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14999 gameMode == EditPosition || gameMode == IcsExamining ||
\r
15000 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
15001 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
15002 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
15003 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
15004 gameMode == Training) PopUpMoveDialog(firstChar);
15008 TypeInDoneEvent(char *move)
15011 int n, fromX, fromY, toX, toY;
15013 ChessMove moveType;
\r
15016 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
15017 EditPositionPasteFEN(move);
\r
15020 // [HGM] movenum: allow move number to be typed in any mode
\r
15021 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
15022 ToNrEvent(2*n-1);
\r
15026 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
15027 gameMode != Training) {
\r
15028 DisplayMoveError(_("Displayed move is not current"));
\r
15030 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15031 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
15032 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
15033 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15034 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
15035 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
15037 DisplayMoveError(_("Could not parse move"));
\r
15043 DisplayMove(moveNumber)
15046 char message[MSG_SIZ];
15048 char cpThinkOutput[MSG_SIZ];
15050 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15052 if (moveNumber == forwardMostMove - 1 ||
15053 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15055 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15057 if (strchr(cpThinkOutput, '\n')) {
15058 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15061 *cpThinkOutput = NULLCHAR;
15064 /* [AS] Hide thinking from human user */
15065 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15066 *cpThinkOutput = NULLCHAR;
15067 if( thinkOutput[0] != NULLCHAR ) {
15070 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15071 cpThinkOutput[i] = '.';
15073 cpThinkOutput[i] = NULLCHAR;
15074 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15078 if (moveNumber == forwardMostMove - 1 &&
15079 gameInfo.resultDetails != NULL) {
15080 if (gameInfo.resultDetails[0] == NULLCHAR) {
15081 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15083 snprintf(res, MSG_SIZ, " {%s} %s",
15084 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15090 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15091 DisplayMessage(res, cpThinkOutput);
15093 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15094 WhiteOnMove(moveNumber) ? " " : ".. ",
15095 parseList[moveNumber], res);
15096 DisplayMessage(message, cpThinkOutput);
15101 DisplayComment(moveNumber, text)
15105 char title[MSG_SIZ];
15106 char buf[8000]; // comment can be long!
15109 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15110 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15112 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15113 WhiteOnMove(moveNumber) ? " " : ".. ",
15114 parseList[moveNumber]);
15116 // [HGM] PV info: display PV info together with (or as) comment
15117 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15118 if(text == NULL) text = "";
15119 score = pvInfoList[moveNumber].score;
15120 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15121 depth, (pvInfoList[moveNumber].time+50)/100, text);
15124 if (text != NULL && (appData.autoDisplayComment || commentUp))
15125 CommentPopUp(title, text);
15128 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15129 * might be busy thinking or pondering. It can be omitted if your
15130 * gnuchess is configured to stop thinking immediately on any user
15131 * input. However, that gnuchess feature depends on the FIONREAD
15132 * ioctl, which does not work properly on some flavors of Unix.
15136 ChessProgramState *cps;
15139 if (!cps->useSigint) return;
15140 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15141 switch (gameMode) {
15142 case MachinePlaysWhite:
15143 case MachinePlaysBlack:
15144 case TwoMachinesPlay:
15145 case IcsPlayingWhite:
15146 case IcsPlayingBlack:
15149 /* Skip if we know it isn't thinking */
15150 if (!cps->maybeThinking) return;
15151 if (appData.debugMode)
15152 fprintf(debugFP, "Interrupting %s\n", cps->which);
15153 InterruptChildProcess(cps->pr);
15154 cps->maybeThinking = FALSE;
15159 #endif /*ATTENTION*/
15165 if (whiteTimeRemaining <= 0) {
15168 if (appData.icsActive) {
15169 if (appData.autoCallFlag &&
15170 gameMode == IcsPlayingBlack && !blackFlag) {
15171 SendToICS(ics_prefix);
15172 SendToICS("flag\n");
15176 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15178 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15179 if (appData.autoCallFlag) {
15180 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15187 if (blackTimeRemaining <= 0) {
15190 if (appData.icsActive) {
15191 if (appData.autoCallFlag &&
15192 gameMode == IcsPlayingWhite && !whiteFlag) {
15193 SendToICS(ics_prefix);
15194 SendToICS("flag\n");
15198 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15200 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15201 if (appData.autoCallFlag) {
15202 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15215 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15216 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15219 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15221 if ( !WhiteOnMove(forwardMostMove) ) {
15222 /* White made time control */
15223 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15224 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15225 /* [HGM] time odds: correct new time quota for time odds! */
15226 / WhitePlayer()->timeOdds;
15227 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15229 lastBlack -= blackTimeRemaining;
15230 /* Black made time control */
15231 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15232 / WhitePlayer()->other->timeOdds;
15233 lastWhite = whiteTimeRemaining;
15238 DisplayBothClocks()
15240 int wom = gameMode == EditPosition ?
15241 !blackPlaysFirst : WhiteOnMove(currentMove);
15242 DisplayWhiteClock(whiteTimeRemaining, wom);
15243 DisplayBlackClock(blackTimeRemaining, !wom);
15247 /* Timekeeping seems to be a portability nightmare. I think everyone
15248 has ftime(), but I'm really not sure, so I'm including some ifdefs
15249 to use other calls if you don't. Clocks will be less accurate if
15250 you have neither ftime nor gettimeofday.
15253 /* VS 2008 requires the #include outside of the function */
15254 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15255 #include <sys/timeb.h>
15258 /* Get the current time as a TimeMark */
15263 #if HAVE_GETTIMEOFDAY
15265 struct timeval timeVal;
15266 struct timezone timeZone;
15268 gettimeofday(&timeVal, &timeZone);
15269 tm->sec = (long) timeVal.tv_sec;
15270 tm->ms = (int) (timeVal.tv_usec / 1000L);
15272 #else /*!HAVE_GETTIMEOFDAY*/
15275 // include <sys/timeb.h> / moved to just above start of function
15276 struct timeb timeB;
15279 tm->sec = (long) timeB.time;
15280 tm->ms = (int) timeB.millitm;
15282 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15283 tm->sec = (long) time(NULL);
15289 /* Return the difference in milliseconds between two
15290 time marks. We assume the difference will fit in a long!
15293 SubtractTimeMarks(tm2, tm1)
15294 TimeMark *tm2, *tm1;
15296 return 1000L*(tm2->sec - tm1->sec) +
15297 (long) (tm2->ms - tm1->ms);
15302 * Code to manage the game clocks.
15304 * In tournament play, black starts the clock and then white makes a move.
15305 * We give the human user a slight advantage if he is playing white---the
15306 * clocks don't run until he makes his first move, so it takes zero time.
15307 * Also, we don't account for network lag, so we could get out of sync
15308 * with GNU Chess's clock -- but then, referees are always right.
15311 static TimeMark tickStartTM;
15312 static long intendedTickLength;
15315 NextTickLength(timeRemaining)
15316 long timeRemaining;
15318 long nominalTickLength, nextTickLength;
15320 if (timeRemaining > 0L && timeRemaining <= 10000L)
15321 nominalTickLength = 100L;
15323 nominalTickLength = 1000L;
15324 nextTickLength = timeRemaining % nominalTickLength;
15325 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15327 return nextTickLength;
15330 /* Adjust clock one minute up or down */
15332 AdjustClock(Boolean which, int dir)
15334 if(which) blackTimeRemaining += 60000*dir;
15335 else whiteTimeRemaining += 60000*dir;
15336 DisplayBothClocks();
15339 /* Stop clocks and reset to a fresh time control */
15343 (void) StopClockTimer();
15344 if (appData.icsActive) {
15345 whiteTimeRemaining = blackTimeRemaining = 0;
15346 } else if (searchTime) {
15347 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15348 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15349 } else { /* [HGM] correct new time quote for time odds */
15350 whiteTC = blackTC = fullTimeControlString;
15351 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15352 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15354 if (whiteFlag || blackFlag) {
15356 whiteFlag = blackFlag = FALSE;
15358 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15359 DisplayBothClocks();
15362 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15364 /* Decrement running clock by amount of time that has passed */
15368 long timeRemaining;
15369 long lastTickLength, fudge;
15372 if (!appData.clockMode) return;
15373 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15377 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15379 /* Fudge if we woke up a little too soon */
15380 fudge = intendedTickLength - lastTickLength;
15381 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15383 if (WhiteOnMove(forwardMostMove)) {
15384 if(whiteNPS >= 0) lastTickLength = 0;
15385 timeRemaining = whiteTimeRemaining -= lastTickLength;
15386 if(timeRemaining < 0 && !appData.icsActive) {
15387 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15388 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15389 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15390 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15393 DisplayWhiteClock(whiteTimeRemaining - fudge,
15394 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15396 if(blackNPS >= 0) lastTickLength = 0;
15397 timeRemaining = blackTimeRemaining -= lastTickLength;
15398 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15399 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15401 blackStartMove = forwardMostMove;
15402 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15405 DisplayBlackClock(blackTimeRemaining - fudge,
15406 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15408 if (CheckFlags()) return;
15411 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15412 StartClockTimer(intendedTickLength);
15414 /* if the time remaining has fallen below the alarm threshold, sound the
15415 * alarm. if the alarm has sounded and (due to a takeback or time control
15416 * with increment) the time remaining has increased to a level above the
15417 * threshold, reset the alarm so it can sound again.
15420 if (appData.icsActive && appData.icsAlarm) {
15422 /* make sure we are dealing with the user's clock */
15423 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15424 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15427 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15428 alarmSounded = FALSE;
15429 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15431 alarmSounded = TRUE;
15437 /* A player has just moved, so stop the previously running
15438 clock and (if in clock mode) start the other one.
15439 We redisplay both clocks in case we're in ICS mode, because
15440 ICS gives us an update to both clocks after every move.
15441 Note that this routine is called *after* forwardMostMove
15442 is updated, so the last fractional tick must be subtracted
15443 from the color that is *not* on move now.
15446 SwitchClocks(int newMoveNr)
15448 long lastTickLength;
15450 int flagged = FALSE;
15454 if (StopClockTimer() && appData.clockMode) {
15455 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15456 if (!WhiteOnMove(forwardMostMove)) {
15457 if(blackNPS >= 0) lastTickLength = 0;
15458 blackTimeRemaining -= lastTickLength;
15459 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15460 // if(pvInfoList[forwardMostMove].time == -1)
15461 pvInfoList[forwardMostMove].time = // use GUI time
15462 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15464 if(whiteNPS >= 0) lastTickLength = 0;
15465 whiteTimeRemaining -= lastTickLength;
15466 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15467 // if(pvInfoList[forwardMostMove].time == -1)
15468 pvInfoList[forwardMostMove].time =
15469 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15471 flagged = CheckFlags();
15473 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15474 CheckTimeControl();
15476 if (flagged || !appData.clockMode) return;
15478 switch (gameMode) {
15479 case MachinePlaysBlack:
15480 case MachinePlaysWhite:
15481 case BeginningOfGame:
15482 if (pausing) return;
15486 case PlayFromGameFile:
15494 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15495 if(WhiteOnMove(forwardMostMove))
15496 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15497 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15501 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15502 whiteTimeRemaining : blackTimeRemaining);
15503 StartClockTimer(intendedTickLength);
15507 /* Stop both clocks */
15511 long lastTickLength;
15514 if (!StopClockTimer()) return;
15515 if (!appData.clockMode) return;
15519 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15520 if (WhiteOnMove(forwardMostMove)) {
15521 if(whiteNPS >= 0) lastTickLength = 0;
15522 whiteTimeRemaining -= lastTickLength;
15523 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15525 if(blackNPS >= 0) lastTickLength = 0;
15526 blackTimeRemaining -= lastTickLength;
15527 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15532 /* Start clock of player on move. Time may have been reset, so
15533 if clock is already running, stop and restart it. */
15537 (void) StopClockTimer(); /* in case it was running already */
15538 DisplayBothClocks();
15539 if (CheckFlags()) return;
15541 if (!appData.clockMode) return;
15542 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15544 GetTimeMark(&tickStartTM);
15545 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15546 whiteTimeRemaining : blackTimeRemaining);
15548 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15549 whiteNPS = blackNPS = -1;
15550 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15551 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15552 whiteNPS = first.nps;
15553 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15554 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15555 blackNPS = first.nps;
15556 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15557 whiteNPS = second.nps;
15558 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15559 blackNPS = second.nps;
15560 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15562 StartClockTimer(intendedTickLength);
15569 long second, minute, hour, day;
15571 static char buf[32];
15573 if (ms > 0 && ms <= 9900) {
15574 /* convert milliseconds to tenths, rounding up */
15575 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15577 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15581 /* convert milliseconds to seconds, rounding up */
15582 /* use floating point to avoid strangeness of integer division
15583 with negative dividends on many machines */
15584 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15591 day = second / (60 * 60 * 24);
15592 second = second % (60 * 60 * 24);
15593 hour = second / (60 * 60);
15594 second = second % (60 * 60);
15595 minute = second / 60;
15596 second = second % 60;
15599 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15600 sign, day, hour, minute, second);
15602 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15604 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15611 * This is necessary because some C libraries aren't ANSI C compliant yet.
15614 StrStr(string, match)
15615 char *string, *match;
15619 length = strlen(match);
15621 for (i = strlen(string) - length; i >= 0; i--, string++)
15622 if (!strncmp(match, string, length))
15629 StrCaseStr(string, match)
15630 char *string, *match;
15634 length = strlen(match);
15636 for (i = strlen(string) - length; i >= 0; i--, string++) {
15637 for (j = 0; j < length; j++) {
15638 if (ToLower(match[j]) != ToLower(string[j]))
15641 if (j == length) return string;
15655 c1 = ToLower(*s1++);
15656 c2 = ToLower(*s2++);
15657 if (c1 > c2) return 1;
15658 if (c1 < c2) return -1;
15659 if (c1 == NULLCHAR) return 0;
15668 return isupper(c) ? tolower(c) : c;
15676 return islower(c) ? toupper(c) : c;
15678 #endif /* !_amigados */
15686 if ((ret = (char *) malloc(strlen(s) + 1)))
15688 safeStrCpy(ret, s, strlen(s)+1);
15694 StrSavePtr(s, savePtr)
15695 char *s, **savePtr;
15700 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15701 safeStrCpy(*savePtr, s, strlen(s)+1);
15713 clock = time((time_t *)NULL);
15714 tm = localtime(&clock);
15715 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15716 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15717 return StrSave(buf);
15722 PositionToFEN(move, overrideCastling)
15724 char *overrideCastling;
15726 int i, j, fromX, fromY, toX, toY;
15733 whiteToPlay = (gameMode == EditPosition) ?
15734 !blackPlaysFirst : (move % 2 == 0);
15737 /* Piece placement data */
15738 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15740 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15741 if (boards[move][i][j] == EmptySquare) {
15743 } else { ChessSquare piece = boards[move][i][j];
15744 if (emptycount > 0) {
15745 if(emptycount<10) /* [HGM] can be >= 10 */
15746 *p++ = '0' + emptycount;
15747 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15750 if(PieceToChar(piece) == '+') {
15751 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15753 piece = (ChessSquare)(DEMOTED piece);
15755 *p++ = PieceToChar(piece);
15757 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15758 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15763 if (emptycount > 0) {
15764 if(emptycount<10) /* [HGM] can be >= 10 */
15765 *p++ = '0' + emptycount;
15766 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15773 /* [HGM] print Crazyhouse or Shogi holdings */
15774 if( gameInfo.holdingsWidth ) {
15775 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15777 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15778 piece = boards[move][i][BOARD_WIDTH-1];
15779 if( piece != EmptySquare )
15780 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15781 *p++ = PieceToChar(piece);
15783 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15784 piece = boards[move][BOARD_HEIGHT-i-1][0];
15785 if( piece != EmptySquare )
15786 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15787 *p++ = PieceToChar(piece);
15790 if( q == p ) *p++ = '-';
15796 *p++ = whiteToPlay ? 'w' : 'b';
15799 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15800 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15802 if(nrCastlingRights) {
15804 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15805 /* [HGM] write directly from rights */
15806 if(boards[move][CASTLING][2] != NoRights &&
15807 boards[move][CASTLING][0] != NoRights )
15808 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15809 if(boards[move][CASTLING][2] != NoRights &&
15810 boards[move][CASTLING][1] != NoRights )
15811 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15812 if(boards[move][CASTLING][5] != NoRights &&
15813 boards[move][CASTLING][3] != NoRights )
15814 *p++ = boards[move][CASTLING][3] + AAA;
15815 if(boards[move][CASTLING][5] != NoRights &&
15816 boards[move][CASTLING][4] != NoRights )
15817 *p++ = boards[move][CASTLING][4] + AAA;
15820 /* [HGM] write true castling rights */
15821 if( nrCastlingRights == 6 ) {
15822 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15823 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15824 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15825 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15826 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15827 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15828 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15829 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15832 if (q == p) *p++ = '-'; /* No castling rights */
15836 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15837 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15838 /* En passant target square */
15839 if (move > backwardMostMove) {
15840 fromX = moveList[move - 1][0] - AAA;
15841 fromY = moveList[move - 1][1] - ONE;
15842 toX = moveList[move - 1][2] - AAA;
15843 toY = moveList[move - 1][3] - ONE;
15844 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15845 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15846 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15848 /* 2-square pawn move just happened */
15850 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15854 } else if(move == backwardMostMove) {
15855 // [HGM] perhaps we should always do it like this, and forget the above?
15856 if((signed char)boards[move][EP_STATUS] >= 0) {
15857 *p++ = boards[move][EP_STATUS] + AAA;
15858 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15869 /* [HGM] find reversible plies */
15870 { int i = 0, j=move;
15872 if (appData.debugMode) { int k;
15873 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15874 for(k=backwardMostMove; k<=forwardMostMove; k++)
15875 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15879 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15880 if( j == backwardMostMove ) i += initialRulePlies;
15881 sprintf(p, "%d ", i);
15882 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15884 /* Fullmove number */
15885 sprintf(p, "%d", (move / 2) + 1);
15887 return StrSave(buf);
15891 ParseFEN(board, blackPlaysFirst, fen)
15893 int *blackPlaysFirst;
15903 /* [HGM] by default clear Crazyhouse holdings, if present */
15904 if(gameInfo.holdingsWidth) {
15905 for(i=0; i<BOARD_HEIGHT; i++) {
15906 board[i][0] = EmptySquare; /* black holdings */
15907 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15908 board[i][1] = (ChessSquare) 0; /* black counts */
15909 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15913 /* Piece placement data */
15914 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15917 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15918 if (*p == '/') p++;
15919 emptycount = gameInfo.boardWidth - j;
15920 while (emptycount--)
15921 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15923 #if(BOARD_FILES >= 10)
15924 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15925 p++; emptycount=10;
15926 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15927 while (emptycount--)
15928 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15930 } else if (isdigit(*p)) {
15931 emptycount = *p++ - '0';
15932 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15933 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15934 while (emptycount--)
15935 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15936 } else if (*p == '+' || isalpha(*p)) {
15937 if (j >= gameInfo.boardWidth) return FALSE;
15939 piece = CharToPiece(*++p);
15940 if(piece == EmptySquare) return FALSE; /* unknown piece */
15941 piece = (ChessSquare) (PROMOTED piece ); p++;
15942 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15943 } else piece = CharToPiece(*p++);
15945 if(piece==EmptySquare) return FALSE; /* unknown piece */
15946 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15947 piece = (ChessSquare) (PROMOTED piece);
15948 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15951 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15957 while (*p == '/' || *p == ' ') p++;
15959 /* [HGM] look for Crazyhouse holdings here */
15960 while(*p==' ') p++;
15961 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15963 if(*p == '-' ) p++; /* empty holdings */ else {
15964 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15965 /* if we would allow FEN reading to set board size, we would */
15966 /* have to add holdings and shift the board read so far here */
15967 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15969 if((int) piece >= (int) BlackPawn ) {
15970 i = (int)piece - (int)BlackPawn;
15971 i = PieceToNumber((ChessSquare)i);
15972 if( i >= gameInfo.holdingsSize ) return FALSE;
15973 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15974 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15976 i = (int)piece - (int)WhitePawn;
15977 i = PieceToNumber((ChessSquare)i);
15978 if( i >= gameInfo.holdingsSize ) return FALSE;
15979 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15980 board[i][BOARD_WIDTH-2]++; /* black holdings */
15987 while(*p == ' ') p++;
15991 if(appData.colorNickNames) {
15992 if( c == appData.colorNickNames[0] ) c = 'w'; else
15993 if( c == appData.colorNickNames[1] ) c = 'b';
15997 *blackPlaysFirst = FALSE;
16000 *blackPlaysFirst = TRUE;
16006 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16007 /* return the extra info in global variiables */
16009 /* set defaults in case FEN is incomplete */
16010 board[EP_STATUS] = EP_UNKNOWN;
16011 for(i=0; i<nrCastlingRights; i++ ) {
16012 board[CASTLING][i] =
16013 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16014 } /* assume possible unless obviously impossible */
16015 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16016 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16017 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16018 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16019 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16020 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16021 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16022 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16025 while(*p==' ') p++;
16026 if(nrCastlingRights) {
16027 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16028 /* castling indicator present, so default becomes no castlings */
16029 for(i=0; i<nrCastlingRights; i++ ) {
16030 board[CASTLING][i] = NoRights;
16033 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16034 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16035 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16036 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16037 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16039 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16040 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16041 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16043 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16044 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16045 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16046 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16047 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16048 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16051 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16052 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16053 board[CASTLING][2] = whiteKingFile;
16056 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16057 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16058 board[CASTLING][2] = whiteKingFile;
16061 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16062 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16063 board[CASTLING][5] = blackKingFile;
16066 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16067 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16068 board[CASTLING][5] = blackKingFile;
16071 default: /* FRC castlings */
16072 if(c >= 'a') { /* black rights */
16073 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16074 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16075 if(i == BOARD_RGHT) break;
16076 board[CASTLING][5] = i;
16078 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16079 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16081 board[CASTLING][3] = c;
16083 board[CASTLING][4] = c;
16084 } else { /* white rights */
16085 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16086 if(board[0][i] == WhiteKing) break;
16087 if(i == BOARD_RGHT) break;
16088 board[CASTLING][2] = i;
16089 c -= AAA - 'a' + 'A';
16090 if(board[0][c] >= WhiteKing) break;
16092 board[CASTLING][0] = c;
16094 board[CASTLING][1] = c;
16098 for(i=0; i<nrCastlingRights; i++)
16099 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16100 if (appData.debugMode) {
16101 fprintf(debugFP, "FEN castling rights:");
16102 for(i=0; i<nrCastlingRights; i++)
16103 fprintf(debugFP, " %d", board[CASTLING][i]);
16104 fprintf(debugFP, "\n");
16107 while(*p==' ') p++;
16110 /* read e.p. field in games that know e.p. capture */
16111 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16112 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16114 p++; board[EP_STATUS] = EP_NONE;
16116 char c = *p++ - AAA;
16118 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16119 if(*p >= '0' && *p <='9') p++;
16120 board[EP_STATUS] = c;
16125 if(sscanf(p, "%d", &i) == 1) {
16126 FENrulePlies = i; /* 50-move ply counter */
16127 /* (The move number is still ignored) */
16134 EditPositionPasteFEN(char *fen)
16137 Board initial_position;
16139 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16140 DisplayError(_("Bad FEN position in clipboard"), 0);
16143 int savedBlackPlaysFirst = blackPlaysFirst;
16144 EditPositionEvent();
16145 blackPlaysFirst = savedBlackPlaysFirst;
16146 CopyBoard(boards[0], initial_position);
16147 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16148 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16149 DisplayBothClocks();
16150 DrawPosition(FALSE, boards[currentMove]);
16155 static char cseq[12] = "\\ ";
16157 Boolean set_cont_sequence(char *new_seq)
16162 // handle bad attempts to set the sequence
16164 return 0; // acceptable error - no debug
16166 len = strlen(new_seq);
16167 ret = (len > 0) && (len < sizeof(cseq));
16169 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16170 else if (appData.debugMode)
16171 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16176 reformat a source message so words don't cross the width boundary. internal
16177 newlines are not removed. returns the wrapped size (no null character unless
16178 included in source message). If dest is NULL, only calculate the size required
16179 for the dest buffer. lp argument indicats line position upon entry, and it's
16180 passed back upon exit.
16182 int wrap(char *dest, char *src, int count, int width, int *lp)
16184 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16186 cseq_len = strlen(cseq);
16187 old_line = line = *lp;
16188 ansi = len = clen = 0;
16190 for (i=0; i < count; i++)
16192 if (src[i] == '\033')
16195 // if we hit the width, back up
16196 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16198 // store i & len in case the word is too long
16199 old_i = i, old_len = len;
16201 // find the end of the last word
16202 while (i && src[i] != ' ' && src[i] != '\n')
16208 // word too long? restore i & len before splitting it
16209 if ((old_i-i+clen) >= width)
16216 if (i && src[i-1] == ' ')
16219 if (src[i] != ' ' && src[i] != '\n')
16226 // now append the newline and continuation sequence
16231 strncpy(dest+len, cseq, cseq_len);
16239 dest[len] = src[i];
16243 if (src[i] == '\n')
16248 if (dest && appData.debugMode)
16250 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16251 count, width, line, len, *lp);
16252 show_bytes(debugFP, src, count);
16253 fprintf(debugFP, "\ndest: ");
16254 show_bytes(debugFP, dest, len);
16255 fprintf(debugFP, "\n");
16257 *lp = dest ? line : old_line;
16262 // [HGM] vari: routines for shelving variations
16265 PushInner(int firstMove, int lastMove)
16267 int i, j, nrMoves = lastMove - firstMove;
16269 // push current tail of game on stack
16270 savedResult[storedGames] = gameInfo.result;
16271 savedDetails[storedGames] = gameInfo.resultDetails;
16272 gameInfo.resultDetails = NULL;
16273 savedFirst[storedGames] = firstMove;
16274 savedLast [storedGames] = lastMove;
16275 savedFramePtr[storedGames] = framePtr;
16276 framePtr -= nrMoves; // reserve space for the boards
16277 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16278 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16279 for(j=0; j<MOVE_LEN; j++)
16280 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16281 for(j=0; j<2*MOVE_LEN; j++)
16282 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16283 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16284 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16285 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16286 pvInfoList[firstMove+i-1].depth = 0;
16287 commentList[framePtr+i] = commentList[firstMove+i];
16288 commentList[firstMove+i] = NULL;
16292 forwardMostMove = firstMove; // truncate game so we can start variation
16296 PushTail(int firstMove, int lastMove)
16298 if(appData.icsActive) { // only in local mode
16299 forwardMostMove = currentMove; // mimic old ICS behavior
16302 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16304 PushInner(firstMove, lastMove);
16305 if(storedGames == 1) GreyRevert(FALSE);
16309 PopInner(Boolean annotate)
16312 char buf[8000], moveBuf[20];
16315 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16316 nrMoves = savedLast[storedGames] - currentMove;
16319 if(!WhiteOnMove(currentMove))
16320 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16321 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16322 for(i=currentMove; i<forwardMostMove; i++) {
16324 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16325 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16326 strcat(buf, moveBuf);
16327 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16328 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16332 for(i=1; i<=nrMoves; i++) { // copy last variation back
16333 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16334 for(j=0; j<MOVE_LEN; j++)
16335 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16336 for(j=0; j<2*MOVE_LEN; j++)
16337 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16338 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16339 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16340 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16341 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16342 commentList[currentMove+i] = commentList[framePtr+i];
16343 commentList[framePtr+i] = NULL;
16345 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16346 framePtr = savedFramePtr[storedGames];
16347 gameInfo.result = savedResult[storedGames];
16348 if(gameInfo.resultDetails != NULL) {
16349 free(gameInfo.resultDetails);
16351 gameInfo.resultDetails = savedDetails[storedGames];
16352 forwardMostMove = currentMove + nrMoves;
16356 PopTail(Boolean annotate)
16358 if(appData.icsActive) return FALSE; // only in local mode
16359 if(!storedGames) return FALSE; // sanity
16360 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16362 PopInner(annotate);
16364 if(storedGames == 0) GreyRevert(TRUE);
16370 { // remove all shelved variations
16372 for(i=0; i<storedGames; i++) {
16373 if(savedDetails[i])
16374 free(savedDetails[i]);
16375 savedDetails[i] = NULL;
16377 for(i=framePtr; i<MAX_MOVES; i++) {
16378 if(commentList[i]) free(commentList[i]);
16379 commentList[i] = NULL;
16381 framePtr = MAX_MOVES-1;
16386 LoadVariation(int index, char *text)
16387 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16388 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16389 int level = 0, move;
16391 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16392 // first find outermost bracketing variation
16393 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16394 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16395 if(*p == '{') wait = '}'; else
16396 if(*p == '[') wait = ']'; else
16397 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16398 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16400 if(*p == wait) wait = NULLCHAR; // closing ]} found
16403 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16404 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16405 end[1] = NULLCHAR; // clip off comment beyond variation
16406 ToNrEvent(currentMove-1);
16407 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16408 // kludge: use ParsePV() to append variation to game
16409 move = currentMove;
16410 ParsePV(start, TRUE, TRUE);
16411 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16412 ClearPremoveHighlights();
16414 ToNrEvent(currentMove+1);