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], buf2[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] = ".";
899 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901 snprintf(command, MSG_SIZ, "%s %s", p, params);
904 appData.chessProgram[i] = strdup(p);
905 appData.isUCI[i] = isUCI;
906 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907 appData.hasOwnBookUCI[i] = hasBook;
908 if(!nickName[0]) useNick = FALSE;
909 if(useNick) ASSIGN(appData.pgnName[i], nickName);
913 q = firstChessProgramNames;
914 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917 quote, p, quote, appData.directory[i],
918 useNick ? " -fn \"" : "",
919 useNick ? nickName : "",
921 v1 ? " -firstProtocolVersion 1" : "",
922 hasBook ? "" : " -fNoOwnBookUCI",
923 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924 storeVariant ? " -variant " : "",
925 storeVariant ? VariantName(gameInfo.variant) : "");
926 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930 ReplaceEngine(cps, i);
936 int matched, min, sec;
938 * Parse timeControl resource
940 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941 appData.movesPerSession)) {
943 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944 DisplayFatalError(buf, 0, 2);
948 * Parse searchTime resource
950 if (*appData.searchTime != NULLCHAR) {
951 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953 searchTime = min * 60;
954 } else if (matched == 2) {
955 searchTime = min * 60 + sec;
958 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959 DisplayFatalError(buf, 0, 2);
968 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971 GetTimeMark(&programStartTime);
972 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976 programStats.ok_to_send = 1;
977 programStats.seen_stat = 0;
980 * Initialize game list
986 * Internet chess server status
988 if (appData.icsActive) {
989 appData.matchMode = FALSE;
990 appData.matchGames = 0;
992 appData.noChessProgram = !appData.zippyPlay;
994 appData.zippyPlay = FALSE;
995 appData.zippyTalk = FALSE;
996 appData.noChessProgram = TRUE;
998 if (*appData.icsHelper != NULLCHAR) {
999 appData.useTelnet = TRUE;
1000 appData.telnetProgram = appData.icsHelper;
1003 appData.zippyTalk = appData.zippyPlay = FALSE;
1006 /* [AS] Initialize pv info list [HGM] and game state */
1010 for( i=0; i<=framePtr; i++ ) {
1011 pvInfoList[i].depth = -1;
1012 boards[i][EP_STATUS] = EP_NONE;
1013 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1019 /* [AS] Adjudication threshold */
1020 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022 InitEngine(&first, 0);
1023 InitEngine(&second, 1);
1026 pairing.which = "pairing"; // pairing engine
1027 pairing.pr = NoProc;
1029 pairing.program = appData.pairingEngine;
1030 pairing.host = "localhost";
1033 if (appData.icsActive) {
1034 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1035 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036 appData.clockMode = FALSE;
1037 first.sendTime = second.sendTime = 0;
1041 /* Override some settings from environment variables, for backward
1042 compatibility. Unfortunately it's not feasible to have the env
1043 vars just set defaults, at least in xboard. Ugh.
1045 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1050 if (!appData.icsActive) {
1054 /* Check for variants that are supported only in ICS mode,
1055 or not at all. Some that are accepted here nevertheless
1056 have bugs; see comments below.
1058 VariantClass variant = StringToVariant(appData.variant);
1060 case VariantBughouse: /* need four players and two boards */
1061 case VariantKriegspiel: /* need to hide pieces and move details */
1062 /* case VariantFischeRandom: (Fabien: moved below) */
1063 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064 if( (len > MSG_SIZ) && appData.debugMode )
1065 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067 DisplayFatalError(buf, 0, 2);
1070 case VariantUnknown:
1071 case VariantLoadable:
1081 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082 if( (len > MSG_SIZ) && appData.debugMode )
1083 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085 DisplayFatalError(buf, 0, 2);
1088 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1089 case VariantFairy: /* [HGM] TestLegality definitely off! */
1090 case VariantGothic: /* [HGM] should work */
1091 case VariantCapablanca: /* [HGM] should work */
1092 case VariantCourier: /* [HGM] initial forced moves not implemented */
1093 case VariantShogi: /* [HGM] could still mate with pawn drop */
1094 case VariantKnightmate: /* [HGM] should work */
1095 case VariantCylinder: /* [HGM] untested */
1096 case VariantFalcon: /* [HGM] untested */
1097 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098 offboard interposition not understood */
1099 case VariantNormal: /* definitely works! */
1100 case VariantWildCastle: /* pieces not automatically shuffled */
1101 case VariantNoCastle: /* pieces not automatically shuffled */
1102 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103 case VariantLosers: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantSuicide: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantGiveaway: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantTwoKings: /* should work */
1110 case VariantAtomic: /* should work except for win condition */
1111 case Variant3Check: /* should work except for win condition */
1112 case VariantShatranj: /* should work except for all win conditions */
1113 case VariantMakruk: /* should work except for daw countdown */
1114 case VariantBerolina: /* might work if TestLegality is off */
1115 case VariantCapaRandom: /* should work */
1116 case VariantJanus: /* should work */
1117 case VariantSuper: /* experimental */
1118 case VariantGreat: /* experimental, requires legality testing to be off */
1119 case VariantSChess: /* S-Chess, should work */
1120 case VariantSpartan: /* should work */
1127 int NextIntegerFromString( char ** str, long * value )
1132 while( *s == ' ' || *s == '\t' ) {
1138 if( *s >= '0' && *s <= '9' ) {
1139 while( *s >= '0' && *s <= '9' ) {
1140 *value = *value * 10 + (*s - '0');
1152 int NextTimeControlFromString( char ** str, long * value )
1155 int result = NextIntegerFromString( str, &temp );
1158 *value = temp * 60; /* Minutes */
1159 if( **str == ':' ) {
1161 result = NextIntegerFromString( str, &temp );
1162 *value += temp; /* Seconds */
1169 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1170 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1171 int result = -1, type = 0; long temp, temp2;
1173 if(**str != ':') return -1; // old params remain in force!
1175 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1176 if( NextIntegerFromString( str, &temp ) ) return -1;
1177 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180 /* time only: incremental or sudden-death time control */
1181 if(**str == '+') { /* increment follows; read it */
1183 if(**str == '!') type = *(*str)++; // Bronstein TC
1184 if(result = NextIntegerFromString( str, &temp2)) return -1;
1185 *inc = temp2 * 1000;
1186 if(**str == '.') { // read fraction of increment
1187 char *start = ++(*str);
1188 if(result = NextIntegerFromString( str, &temp2)) return -1;
1190 while(start++ < *str) temp2 /= 10;
1194 *moves = 0; *tc = temp * 1000; *incType = type;
1198 (*str)++; /* classical time control */
1199 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1211 { /* [HGM] get time to add from the multi-session time-control string */
1212 int incType, moves=1; /* kludge to force reading of first session */
1213 long time, increment;
1216 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1217 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1219 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1220 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1221 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1222 if(movenr == -1) return time; /* last move before new session */
1223 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1224 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1225 if(!moves) return increment; /* current session is incremental */
1226 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1227 } while(movenr >= -1); /* try again for next session */
1229 return 0; // no new time quota on this move
1233 ParseTimeControl(tc, ti, mps)
1240 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1244 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1245 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1249 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1251 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1256 snprintf(buf, MSG_SIZ, ":%s", mytc);
1258 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1260 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1265 /* Parse second time control */
1268 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1276 timeControl_2 = tc2 * 1000;
1286 timeControl = tc1 * 1000;
1289 timeIncrement = ti * 1000; /* convert to ms */
1290 movesPerSession = 0;
1293 movesPerSession = mps;
1301 if (appData.debugMode) {
1302 fprintf(debugFP, "%s\n", programVersion);
1305 set_cont_sequence(appData.wrapContSeq);
1306 if (appData.matchGames > 0) {
1307 appData.matchMode = TRUE;
1308 } else if (appData.matchMode) {
1309 appData.matchGames = 1;
1311 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1312 appData.matchGames = appData.sameColorGames;
1313 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1314 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1315 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318 if (appData.noChessProgram || first.protocolVersion == 1) {
1321 /* kludge: allow timeout for initial "feature" commands */
1323 DisplayMessage("", _("Starting chess program"));
1324 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1329 CalculateIndex(int index, int gameNr)
1330 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1332 if(index > 0) return index; // fixed nmber
1333 if(index == 0) return 1;
1334 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1335 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1340 LoadGameOrPosition(int gameNr)
1341 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1342 if (*appData.loadGameFile != NULLCHAR) {
1343 if (!LoadGameFromFile(appData.loadGameFile,
1344 CalculateIndex(appData.loadGameIndex, gameNr),
1345 appData.loadGameFile, FALSE)) {
1346 DisplayFatalError(_("Bad game file"), 0, 1);
1349 } else if (*appData.loadPositionFile != NULLCHAR) {
1350 if (!LoadPositionFromFile(appData.loadPositionFile,
1351 CalculateIndex(appData.loadPositionIndex, gameNr),
1352 appData.loadPositionFile)) {
1353 DisplayFatalError(_("Bad position file"), 0, 1);
1361 ReserveGame(int gameNr, char resChar)
1363 FILE *tf = fopen(appData.tourneyFile, "r+");
1364 char *p, *q, c, buf[MSG_SIZ];
1365 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1366 safeStrCpy(buf, lastMsg, MSG_SIZ);
1367 DisplayMessage(_("Pick new game"), "");
1368 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1369 ParseArgsFromFile(tf);
1370 p = q = appData.results;
1371 if(appData.debugMode) {
1372 char *r = appData.participants;
1373 fprintf(debugFP, "results = '%s'\n", p);
1374 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1375 fprintf(debugFP, "\n");
1377 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1379 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1380 safeStrCpy(q, p, strlen(p) + 2);
1381 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1382 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1383 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1384 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387 fseek(tf, -(strlen(p)+4), SEEK_END);
1389 if(c != '"') // depending on DOS or Unix line endings we can be one off
1390 fseek(tf, -(strlen(p)+2), SEEK_END);
1391 else fseek(tf, -(strlen(p)+3), SEEK_END);
1392 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1393 DisplayMessage(buf, "");
1394 free(p); appData.results = q;
1395 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1396 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1397 UnloadEngine(&first); // next game belongs to other pairing;
1398 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1403 MatchEvent(int mode)
1404 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1406 if(matchMode) { // already in match mode: switch it off
1408 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411 // if(gameMode != BeginningOfGame) {
1412 // DisplayError(_("You can only start a match from the initial position."), 0);
1416 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1417 /* Set up machine vs. machine match */
1419 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1420 if(appData.tourneyFile[0]) {
1422 if(nextGame > appData.matchGames) {
1424 if(strchr(appData.results, '*') == NULL) {
1426 appData.tourneyCycles++;
1427 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1429 NextTourneyGame(-1, &dummy);
1431 if(nextGame <= appData.matchGames) {
1432 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1434 ScheduleDelayedEvent(NextMatchGame, 10000);
1439 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1440 DisplayError(buf, 0);
1441 appData.tourneyFile[0] = 0;
1445 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1446 DisplayFatalError(_("Can't have a match with no chess programs"),
1451 matchGame = roundNr = 1;
1452 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1457 InitBackEnd3 P((void))
1459 GameMode initialMode;
1463 InitChessProgram(&first, startedFromSetupPosition);
1465 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1466 free(programVersion);
1467 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1468 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471 if (appData.icsActive) {
1473 /* [DM] Make a console window if needed [HGM] merged ifs */
1479 if (*appData.icsCommPort != NULLCHAR)
1480 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1481 appData.icsCommPort);
1483 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1484 appData.icsHost, appData.icsPort);
1486 if( (len > MSG_SIZ) && appData.debugMode )
1487 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1489 DisplayFatalError(buf, err, 1);
1494 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1496 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1497 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1498 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1499 } else if (appData.noChessProgram) {
1505 if (*appData.cmailGameName != NULLCHAR) {
1507 OpenLoopback(&cmailPR);
1509 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1513 DisplayMessage("", "");
1514 if (StrCaseCmp(appData.initialMode, "") == 0) {
1515 initialMode = BeginningOfGame;
1516 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1517 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1518 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1519 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1523 initialMode = TwoMachinesPlay;
1524 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1525 initialMode = AnalyzeFile;
1526 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1527 initialMode = AnalyzeMode;
1528 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1529 initialMode = MachinePlaysWhite;
1530 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1531 initialMode = MachinePlaysBlack;
1532 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1533 initialMode = EditGame;
1534 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1535 initialMode = EditPosition;
1536 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1537 initialMode = Training;
1539 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1540 if( (len > MSG_SIZ) && appData.debugMode )
1541 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1543 DisplayFatalError(buf, 0, 2);
1547 if (appData.matchMode) {
1548 if(appData.tourneyFile[0]) { // start tourney from command line
1550 if(f = fopen(appData.tourneyFile, "r")) {
1551 ParseArgsFromFile(f); // make sure tourney parmeters re known
1553 appData.clockMode = TRUE;
1555 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558 } else if (*appData.cmailGameName != NULLCHAR) {
1559 /* Set up cmail mode */
1560 ReloadCmailMsgEvent(TRUE);
1562 /* Set up other modes */
1563 if (initialMode == AnalyzeFile) {
1564 if (*appData.loadGameFile == NULLCHAR) {
1565 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1569 if (*appData.loadGameFile != NULLCHAR) {
1570 (void) LoadGameFromFile(appData.loadGameFile,
1571 appData.loadGameIndex,
1572 appData.loadGameFile, TRUE);
1573 } else if (*appData.loadPositionFile != NULLCHAR) {
1574 (void) LoadPositionFromFile(appData.loadPositionFile,
1575 appData.loadPositionIndex,
1576 appData.loadPositionFile);
1577 /* [HGM] try to make self-starting even after FEN load */
1578 /* to allow automatic setup of fairy variants with wtm */
1579 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1580 gameMode = BeginningOfGame;
1581 setboardSpoiledMachineBlack = 1;
1583 /* [HGM] loadPos: make that every new game uses the setup */
1584 /* from file as long as we do not switch variant */
1585 if(!blackPlaysFirst) {
1586 startedFromPositionFile = TRUE;
1587 CopyBoard(filePosition, boards[0]);
1590 if (initialMode == AnalyzeMode) {
1591 if (appData.noChessProgram) {
1592 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595 if (appData.icsActive) {
1596 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1600 } else if (initialMode == AnalyzeFile) {
1601 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1602 ShowThinkingEvent();
1604 AnalysisPeriodicEvent(1);
1605 } else if (initialMode == MachinePlaysWhite) {
1606 if (appData.noChessProgram) {
1607 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1611 if (appData.icsActive) {
1612 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1616 MachineWhiteEvent();
1617 } else if (initialMode == MachinePlaysBlack) {
1618 if (appData.noChessProgram) {
1619 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1623 if (appData.icsActive) {
1624 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1628 MachineBlackEvent();
1629 } else if (initialMode == TwoMachinesPlay) {
1630 if (appData.noChessProgram) {
1631 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1635 if (appData.icsActive) {
1636 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1641 } else if (initialMode == EditGame) {
1643 } else if (initialMode == EditPosition) {
1644 EditPositionEvent();
1645 } else if (initialMode == Training) {
1646 if (*appData.loadGameFile == NULLCHAR) {
1647 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1656 * Establish will establish a contact to a remote host.port.
1657 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1658 * used to talk to the host.
1659 * Returns 0 if okay, error code if not.
1666 if (*appData.icsCommPort != NULLCHAR) {
1667 /* Talk to the host through a serial comm port */
1668 return OpenCommPort(appData.icsCommPort, &icsPR);
1670 } else if (*appData.gateway != NULLCHAR) {
1671 if (*appData.remoteShell == NULLCHAR) {
1672 /* Use the rcmd protocol to run telnet program on a gateway host */
1673 snprintf(buf, sizeof(buf), "%s %s %s",
1674 appData.telnetProgram, appData.icsHost, appData.icsPort);
1675 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1678 /* Use the rsh program to run telnet program on a gateway host */
1679 if (*appData.remoteUser == NULLCHAR) {
1680 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1681 appData.gateway, appData.telnetProgram,
1682 appData.icsHost, appData.icsPort);
1684 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1685 appData.remoteShell, appData.gateway,
1686 appData.remoteUser, appData.telnetProgram,
1687 appData.icsHost, appData.icsPort);
1689 return StartChildProcess(buf, "", &icsPR);
1692 } else if (appData.useTelnet) {
1693 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1696 /* TCP socket interface differs somewhat between
1697 Unix and NT; handle details in the front end.
1699 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1703 void EscapeExpand(char *p, char *q)
1704 { // [HGM] initstring: routine to shape up string arguments
1705 while(*p++ = *q++) if(p[-1] == '\\')
1707 case 'n': p[-1] = '\n'; break;
1708 case 'r': p[-1] = '\r'; break;
1709 case 't': p[-1] = '\t'; break;
1710 case '\\': p[-1] = '\\'; break;
1711 case 0: *p = 0; return;
1712 default: p[-1] = q[-1]; break;
1717 show_bytes(fp, buf, count)
1723 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1724 fprintf(fp, "\\%03o", *buf & 0xff);
1733 /* Returns an errno value */
1735 OutputMaybeTelnet(pr, message, count, outError)
1741 char buf[8192], *p, *q, *buflim;
1742 int left, newcount, outcount;
1744 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1745 *appData.gateway != NULLCHAR) {
1746 if (appData.debugMode) {
1747 fprintf(debugFP, ">ICS: ");
1748 show_bytes(debugFP, message, count);
1749 fprintf(debugFP, "\n");
1751 return OutputToProcess(pr, message, count, outError);
1754 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1761 if (appData.debugMode) {
1762 fprintf(debugFP, ">ICS: ");
1763 show_bytes(debugFP, buf, newcount);
1764 fprintf(debugFP, "\n");
1766 outcount = OutputToProcess(pr, buf, newcount, outError);
1767 if (outcount < newcount) return -1; /* to be sure */
1774 } else if (((unsigned char) *p) == TN_IAC) {
1775 *q++ = (char) TN_IAC;
1782 if (appData.debugMode) {
1783 fprintf(debugFP, ">ICS: ");
1784 show_bytes(debugFP, buf, newcount);
1785 fprintf(debugFP, "\n");
1787 outcount = OutputToProcess(pr, buf, newcount, outError);
1788 if (outcount < newcount) return -1; /* to be sure */
1793 read_from_player(isr, closure, message, count, error)
1800 int outError, outCount;
1801 static int gotEof = 0;
1803 /* Pass data read from player on to ICS */
1806 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1807 if (outCount < count) {
1808 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1810 } else if (count < 0) {
1811 RemoveInputSource(isr);
1812 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1813 } else if (gotEof++ > 0) {
1814 RemoveInputSource(isr);
1815 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1821 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1822 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1823 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1824 SendToICS("date\n");
1825 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1828 /* added routine for printf style output to ics */
1829 void ics_printf(char *format, ...)
1831 char buffer[MSG_SIZ];
1834 va_start(args, format);
1835 vsnprintf(buffer, sizeof(buffer), format, args);
1836 buffer[sizeof(buffer)-1] = '\0';
1845 int count, outCount, outError;
1847 if (icsPR == NULL) return;
1850 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1851 if (outCount < count) {
1852 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856 /* This is used for sending logon scripts to the ICS. Sending
1857 without a delay causes problems when using timestamp on ICC
1858 (at least on my machine). */
1860 SendToICSDelayed(s,msdelay)
1864 int count, outCount, outError;
1866 if (icsPR == NULL) return;
1869 if (appData.debugMode) {
1870 fprintf(debugFP, ">ICS: ");
1871 show_bytes(debugFP, s, count);
1872 fprintf(debugFP, "\n");
1874 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876 if (outCount < count) {
1877 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1882 /* Remove all highlighting escape sequences in s
1883 Also deletes any suffix starting with '('
1886 StripHighlightAndTitle(s)
1889 static char retbuf[MSG_SIZ];
1892 while (*s != NULLCHAR) {
1893 while (*s == '\033') {
1894 while (*s != NULLCHAR && !isalpha(*s)) s++;
1895 if (*s != NULLCHAR) s++;
1897 while (*s != NULLCHAR && *s != '\033') {
1898 if (*s == '(' || *s == '[') {
1909 /* Remove all highlighting escape sequences in s */
1914 static char retbuf[MSG_SIZ];
1917 while (*s != NULLCHAR) {
1918 while (*s == '\033') {
1919 while (*s != NULLCHAR && !isalpha(*s)) s++;
1920 if (*s != NULLCHAR) s++;
1922 while (*s != NULLCHAR && *s != '\033') {
1930 char *variantNames[] = VARIANT_NAMES;
1935 return variantNames[v];
1939 /* Identify a variant from the strings the chess servers use or the
1940 PGN Variant tag names we use. */
1947 VariantClass v = VariantNormal;
1948 int i, found = FALSE;
1954 /* [HGM] skip over optional board-size prefixes */
1955 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1956 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1957 while( *e++ != '_');
1960 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1964 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1965 if (StrCaseStr(e, variantNames[i])) {
1966 v = (VariantClass) i;
1973 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1974 || StrCaseStr(e, "wild/fr")
1975 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1976 v = VariantFischeRandom;
1977 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1978 (i = 1, p = StrCaseStr(e, "w"))) {
1980 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1987 case 0: /* FICS only, actually */
1989 /* Castling legal even if K starts on d-file */
1990 v = VariantWildCastle;
1995 /* Castling illegal even if K & R happen to start in
1996 normal positions. */
1997 v = VariantNoCastle;
2010 /* Castling legal iff K & R start in normal positions */
2016 /* Special wilds for position setup; unclear what to do here */
2017 v = VariantLoadable;
2020 /* Bizarre ICC game */
2021 v = VariantTwoKings;
2024 v = VariantKriegspiel;
2030 v = VariantFischeRandom;
2033 v = VariantCrazyhouse;
2036 v = VariantBughouse;
2042 /* Not quite the same as FICS suicide! */
2043 v = VariantGiveaway;
2049 v = VariantShatranj;
2052 /* Temporary names for future ICC types. The name *will* change in
2053 the next xboard/WinBoard release after ICC defines it. */
2091 v = VariantCapablanca;
2094 v = VariantKnightmate;
2100 v = VariantCylinder;
2106 v = VariantCapaRandom;
2109 v = VariantBerolina;
2121 /* Found "wild" or "w" in the string but no number;
2122 must assume it's normal chess. */
2126 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2127 if( (len > MSG_SIZ) && appData.debugMode )
2128 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2130 DisplayError(buf, 0);
2136 if (appData.debugMode) {
2137 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2138 e, wnum, VariantName(v));
2143 static int leftover_start = 0, leftover_len = 0;
2144 char star_match[STAR_MATCH_N][MSG_SIZ];
2146 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2147 advance *index beyond it, and set leftover_start to the new value of
2148 *index; else return FALSE. If pattern contains the character '*', it
2149 matches any sequence of characters not containing '\r', '\n', or the
2150 character following the '*' (if any), and the matched sequence(s) are
2151 copied into star_match.
2154 looking_at(buf, index, pattern)
2159 char *bufp = &buf[*index], *patternp = pattern;
2161 char *matchp = star_match[0];
2164 if (*patternp == NULLCHAR) {
2165 *index = leftover_start = bufp - buf;
2169 if (*bufp == NULLCHAR) return FALSE;
2170 if (*patternp == '*') {
2171 if (*bufp == *(patternp + 1)) {
2173 matchp = star_match[++star_count];
2177 } else if (*bufp == '\n' || *bufp == '\r') {
2179 if (*patternp == NULLCHAR)
2184 *matchp++ = *bufp++;
2188 if (*patternp != *bufp) return FALSE;
2195 SendToPlayer(data, length)
2199 int error, outCount;
2200 outCount = OutputToProcess(NoProc, data, length, &error);
2201 if (outCount < length) {
2202 DisplayFatalError(_("Error writing to display"), error, 1);
2207 PackHolding(packed, holding)
2219 switch (runlength) {
2230 sprintf(q, "%d", runlength);
2242 /* Telnet protocol requests from the front end */
2244 TelnetRequest(ddww, option)
2245 unsigned char ddww, option;
2247 unsigned char msg[3];
2248 int outCount, outError;
2250 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2252 if (appData.debugMode) {
2253 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2269 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2278 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2281 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2286 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2288 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2295 if (!appData.icsActive) return;
2296 TelnetRequest(TN_DO, TN_ECHO);
2302 if (!appData.icsActive) return;
2303 TelnetRequest(TN_DONT, TN_ECHO);
2307 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2309 /* put the holdings sent to us by the server on the board holdings area */
2310 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2314 if(gameInfo.holdingsWidth < 2) return;
2315 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2316 return; // prevent overwriting by pre-board holdings
2318 if( (int)lowestPiece >= BlackPawn ) {
2321 holdingsStartRow = BOARD_HEIGHT-1;
2324 holdingsColumn = BOARD_WIDTH-1;
2325 countsColumn = BOARD_WIDTH-2;
2326 holdingsStartRow = 0;
2330 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2331 board[i][holdingsColumn] = EmptySquare;
2332 board[i][countsColumn] = (ChessSquare) 0;
2334 while( (p=*holdings++) != NULLCHAR ) {
2335 piece = CharToPiece( ToUpper(p) );
2336 if(piece == EmptySquare) continue;
2337 /*j = (int) piece - (int) WhitePawn;*/
2338 j = PieceToNumber(piece);
2339 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2340 if(j < 0) continue; /* should not happen */
2341 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2342 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2343 board[holdingsStartRow+j*direction][countsColumn]++;
2349 VariantSwitch(Board board, VariantClass newVariant)
2351 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2352 static Board oldBoard;
2354 startedFromPositionFile = FALSE;
2355 if(gameInfo.variant == newVariant) return;
2357 /* [HGM] This routine is called each time an assignment is made to
2358 * gameInfo.variant during a game, to make sure the board sizes
2359 * are set to match the new variant. If that means adding or deleting
2360 * holdings, we shift the playing board accordingly
2361 * This kludge is needed because in ICS observe mode, we get boards
2362 * of an ongoing game without knowing the variant, and learn about the
2363 * latter only later. This can be because of the move list we requested,
2364 * in which case the game history is refilled from the beginning anyway,
2365 * but also when receiving holdings of a crazyhouse game. In the latter
2366 * case we want to add those holdings to the already received position.
2370 if (appData.debugMode) {
2371 fprintf(debugFP, "Switch board from %s to %s\n",
2372 VariantName(gameInfo.variant), VariantName(newVariant));
2373 setbuf(debugFP, NULL);
2375 shuffleOpenings = 0; /* [HGM] shuffle */
2376 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2380 newWidth = 9; newHeight = 9;
2381 gameInfo.holdingsSize = 7;
2382 case VariantBughouse:
2383 case VariantCrazyhouse:
2384 newHoldingsWidth = 2; break;
2388 newHoldingsWidth = 2;
2389 gameInfo.holdingsSize = 8;
2392 case VariantCapablanca:
2393 case VariantCapaRandom:
2396 newHoldingsWidth = gameInfo.holdingsSize = 0;
2399 if(newWidth != gameInfo.boardWidth ||
2400 newHeight != gameInfo.boardHeight ||
2401 newHoldingsWidth != gameInfo.holdingsWidth ) {
2403 /* shift position to new playing area, if needed */
2404 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2405 for(i=0; i<BOARD_HEIGHT; i++)
2406 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2407 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2409 for(i=0; i<newHeight; i++) {
2410 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2411 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2413 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2414 for(i=0; i<BOARD_HEIGHT; i++)
2415 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2416 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419 gameInfo.boardWidth = newWidth;
2420 gameInfo.boardHeight = newHeight;
2421 gameInfo.holdingsWidth = newHoldingsWidth;
2422 gameInfo.variant = newVariant;
2423 InitDrawingSizes(-2, 0);
2424 } else gameInfo.variant = newVariant;
2425 CopyBoard(oldBoard, board); // remember correctly formatted board
2426 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2427 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2430 static int loggedOn = FALSE;
2432 /*-- Game start info cache: --*/
2434 char gs_kind[MSG_SIZ];
2435 static char player1Name[128] = "";
2436 static char player2Name[128] = "";
2437 static char cont_seq[] = "\n\\ ";
2438 static int player1Rating = -1;
2439 static int player2Rating = -1;
2440 /*----------------------------*/
2442 ColorClass curColor = ColorNormal;
2443 int suppressKibitz = 0;
2446 Boolean soughtPending = FALSE;
2447 Boolean seekGraphUp;
2448 #define MAX_SEEK_ADS 200
2450 char *seekAdList[MAX_SEEK_ADS];
2451 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2452 float tcList[MAX_SEEK_ADS];
2453 char colorList[MAX_SEEK_ADS];
2454 int nrOfSeekAds = 0;
2455 int minRating = 1010, maxRating = 2800;
2456 int hMargin = 10, vMargin = 20, h, w;
2457 extern int squareSize, lineGap;
2462 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2463 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2464 if(r < minRating+100 && r >=0 ) r = minRating+100;
2465 if(r > maxRating) r = maxRating;
2466 if(tc < 1.) tc = 1.;
2467 if(tc > 95.) tc = 95.;
2468 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2469 y = ((double)r - minRating)/(maxRating - minRating)
2470 * (h-vMargin-squareSize/8-1) + vMargin;
2471 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2472 if(strstr(seekAdList[i], " u ")) color = 1;
2473 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2474 !strstr(seekAdList[i], "bullet") &&
2475 !strstr(seekAdList[i], "blitz") &&
2476 !strstr(seekAdList[i], "standard") ) color = 2;
2477 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2478 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2482 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2484 char buf[MSG_SIZ], *ext = "";
2485 VariantClass v = StringToVariant(type);
2486 if(strstr(type, "wild")) {
2487 ext = type + 4; // append wild number
2488 if(v == VariantFischeRandom) type = "chess960"; else
2489 if(v == VariantLoadable) type = "setup"; else
2490 type = VariantName(v);
2492 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2493 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2494 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2495 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2496 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2497 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2498 seekNrList[nrOfSeekAds] = nr;
2499 zList[nrOfSeekAds] = 0;
2500 seekAdList[nrOfSeekAds++] = StrSave(buf);
2501 if(plot) PlotSeekAd(nrOfSeekAds-1);
2508 int x = xList[i], y = yList[i], d=squareSize/4, k;
2509 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2510 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2511 // now replot every dot that overlapped
2512 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2513 int xx = xList[k], yy = yList[k];
2514 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2515 DrawSeekDot(xx, yy, colorList[k]);
2520 RemoveSeekAd(int nr)
2523 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2525 if(seekAdList[i]) free(seekAdList[i]);
2526 seekAdList[i] = seekAdList[--nrOfSeekAds];
2527 seekNrList[i] = seekNrList[nrOfSeekAds];
2528 ratingList[i] = ratingList[nrOfSeekAds];
2529 colorList[i] = colorList[nrOfSeekAds];
2530 tcList[i] = tcList[nrOfSeekAds];
2531 xList[i] = xList[nrOfSeekAds];
2532 yList[i] = yList[nrOfSeekAds];
2533 zList[i] = zList[nrOfSeekAds];
2534 seekAdList[nrOfSeekAds] = NULL;
2540 MatchSoughtLine(char *line)
2542 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2543 int nr, base, inc, u=0; char dummy;
2545 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2546 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2548 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2549 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2550 // match: compact and save the line
2551 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2561 if(!seekGraphUp) return FALSE;
2562 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2563 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2565 DrawSeekBackground(0, 0, w, h);
2566 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2567 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2568 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2569 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2571 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2574 snprintf(buf, MSG_SIZ, "%d", i);
2575 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2578 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2579 for(i=1; i<100; i+=(i<10?1:5)) {
2580 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2581 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2582 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2584 snprintf(buf, MSG_SIZ, "%d", i);
2585 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2588 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2592 int SeekGraphClick(ClickType click, int x, int y, int moving)
2594 static int lastDown = 0, displayed = 0, lastSecond;
2595 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2596 if(click == Release || moving) return FALSE;
2598 soughtPending = TRUE;
2599 SendToICS(ics_prefix);
2600 SendToICS("sought\n"); // should this be "sought all"?
2601 } else { // issue challenge based on clicked ad
2602 int dist = 10000; int i, closest = 0, second = 0;
2603 for(i=0; i<nrOfSeekAds; i++) {
2604 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2605 if(d < dist) { dist = d; closest = i; }
2606 second += (d - zList[i] < 120); // count in-range ads
2607 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2611 second = (second > 1);
2612 if(displayed != closest || second != lastSecond) {
2613 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2614 lastSecond = second; displayed = closest;
2616 if(click == Press) {
2617 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2620 } // on press 'hit', only show info
2621 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2622 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2623 SendToICS(ics_prefix);
2625 return TRUE; // let incoming board of started game pop down the graph
2626 } else if(click == Release) { // release 'miss' is ignored
2627 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2628 if(moving == 2) { // right up-click
2629 nrOfSeekAds = 0; // refresh graph
2630 soughtPending = TRUE;
2631 SendToICS(ics_prefix);
2632 SendToICS("sought\n"); // should this be "sought all"?
2635 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2636 // press miss or release hit 'pop down' seek graph
2637 seekGraphUp = FALSE;
2638 DrawPosition(TRUE, NULL);
2644 read_from_ics(isr, closure, data, count, error)
2651 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2652 #define STARTED_NONE 0
2653 #define STARTED_MOVES 1
2654 #define STARTED_BOARD 2
2655 #define STARTED_OBSERVE 3
2656 #define STARTED_HOLDINGS 4
2657 #define STARTED_CHATTER 5
2658 #define STARTED_COMMENT 6
2659 #define STARTED_MOVES_NOHIDE 7
2661 static int started = STARTED_NONE;
2662 static char parse[20000];
2663 static int parse_pos = 0;
2664 static char buf[BUF_SIZE + 1];
2665 static int firstTime = TRUE, intfSet = FALSE;
2666 static ColorClass prevColor = ColorNormal;
2667 static int savingComment = FALSE;
2668 static int cmatch = 0; // continuation sequence match
2675 int backup; /* [DM] For zippy color lines */
2677 char talker[MSG_SIZ]; // [HGM] chat
2680 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2682 if (appData.debugMode) {
2684 fprintf(debugFP, "<ICS: ");
2685 show_bytes(debugFP, data, count);
2686 fprintf(debugFP, "\n");
2690 if (appData.debugMode) { int f = forwardMostMove;
2691 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2692 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2693 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2696 /* If last read ended with a partial line that we couldn't parse,
2697 prepend it to the new read and try again. */
2698 if (leftover_len > 0) {
2699 for (i=0; i<leftover_len; i++)
2700 buf[i] = buf[leftover_start + i];
2703 /* copy new characters into the buffer */
2704 bp = buf + leftover_len;
2705 buf_len=leftover_len;
2706 for (i=0; i<count; i++)
2709 if (data[i] == '\r')
2712 // join lines split by ICS?
2713 if (!appData.noJoin)
2716 Joining just consists of finding matches against the
2717 continuation sequence, and discarding that sequence
2718 if found instead of copying it. So, until a match
2719 fails, there's nothing to do since it might be the
2720 complete sequence, and thus, something we don't want
2723 if (data[i] == cont_seq[cmatch])
2726 if (cmatch == strlen(cont_seq))
2728 cmatch = 0; // complete match. just reset the counter
2731 it's possible for the ICS to not include the space
2732 at the end of the last word, making our [correct]
2733 join operation fuse two separate words. the server
2734 does this when the space occurs at the width setting.
2736 if (!buf_len || buf[buf_len-1] != ' ')
2747 match failed, so we have to copy what matched before
2748 falling through and copying this character. In reality,
2749 this will only ever be just the newline character, but
2750 it doesn't hurt to be precise.
2752 strncpy(bp, cont_seq, cmatch);
2764 buf[buf_len] = NULLCHAR;
2765 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2770 while (i < buf_len) {
2771 /* Deal with part of the TELNET option negotiation
2772 protocol. We refuse to do anything beyond the
2773 defaults, except that we allow the WILL ECHO option,
2774 which ICS uses to turn off password echoing when we are
2775 directly connected to it. We reject this option
2776 if localLineEditing mode is on (always on in xboard)
2777 and we are talking to port 23, which might be a real
2778 telnet server that will try to keep WILL ECHO on permanently.
2780 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2781 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2782 unsigned char option;
2784 switch ((unsigned char) buf[++i]) {
2786 if (appData.debugMode)
2787 fprintf(debugFP, "\n<WILL ");
2788 switch (option = (unsigned char) buf[++i]) {
2790 if (appData.debugMode)
2791 fprintf(debugFP, "ECHO ");
2792 /* Reply only if this is a change, according
2793 to the protocol rules. */
2794 if (remoteEchoOption) break;
2795 if (appData.localLineEditing &&
2796 atoi(appData.icsPort) == TN_PORT) {
2797 TelnetRequest(TN_DONT, TN_ECHO);
2800 TelnetRequest(TN_DO, TN_ECHO);
2801 remoteEchoOption = TRUE;
2805 if (appData.debugMode)
2806 fprintf(debugFP, "%d ", option);
2807 /* Whatever this is, we don't want it. */
2808 TelnetRequest(TN_DONT, option);
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<WONT ");
2815 switch (option = (unsigned char) buf[++i]) {
2817 if (appData.debugMode)
2818 fprintf(debugFP, "ECHO ");
2819 /* Reply only if this is a change, according
2820 to the protocol rules. */
2821 if (!remoteEchoOption) break;
2823 TelnetRequest(TN_DONT, TN_ECHO);
2824 remoteEchoOption = FALSE;
2827 if (appData.debugMode)
2828 fprintf(debugFP, "%d ", (unsigned char) option);
2829 /* Whatever this is, it must already be turned
2830 off, because we never agree to turn on
2831 anything non-default, so according to the
2832 protocol rules, we don't reply. */
2837 if (appData.debugMode)
2838 fprintf(debugFP, "\n<DO ");
2839 switch (option = (unsigned char) buf[++i]) {
2841 /* Whatever this is, we refuse to do it. */
2842 if (appData.debugMode)
2843 fprintf(debugFP, "%d ", option);
2844 TelnetRequest(TN_WONT, option);
2849 if (appData.debugMode)
2850 fprintf(debugFP, "\n<DONT ");
2851 switch (option = (unsigned char) buf[++i]) {
2853 if (appData.debugMode)
2854 fprintf(debugFP, "%d ", option);
2855 /* Whatever this is, we are already not doing
2856 it, because we never agree to do anything
2857 non-default, so according to the protocol
2858 rules, we don't reply. */
2863 if (appData.debugMode)
2864 fprintf(debugFP, "\n<IAC ");
2865 /* Doubled IAC; pass it through */
2869 if (appData.debugMode)
2870 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2871 /* Drop all other telnet commands on the floor */
2874 if (oldi > next_out)
2875 SendToPlayer(&buf[next_out], oldi - next_out);
2881 /* OK, this at least will *usually* work */
2882 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2886 if (loggedOn && !intfSet) {
2887 if (ics_type == ICS_ICC) {
2888 snprintf(str, MSG_SIZ,
2889 "/set-quietly interface %s\n/set-quietly style 12\n",
2891 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2892 strcat(str, "/set-2 51 1\n/set seek 1\n");
2893 } else if (ics_type == ICS_CHESSNET) {
2894 snprintf(str, MSG_SIZ, "/style 12\n");
2896 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2897 strcat(str, programVersion);
2898 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2899 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2900 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2902 strcat(str, "$iset nohighlight 1\n");
2904 strcat(str, "$iset lock 1\n$style 12\n");
2907 NotifyFrontendLogin();
2911 if (started == STARTED_COMMENT) {
2912 /* Accumulate characters in comment */
2913 parse[parse_pos++] = buf[i];
2914 if (buf[i] == '\n') {
2915 parse[parse_pos] = NULLCHAR;
2916 if(chattingPartner>=0) {
2918 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2919 OutputChatMessage(chattingPartner, mess);
2920 chattingPartner = -1;
2921 next_out = i+1; // [HGM] suppress printing in ICS window
2923 if(!suppressKibitz) // [HGM] kibitz
2924 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2925 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2926 int nrDigit = 0, nrAlph = 0, j;
2927 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2928 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2929 parse[parse_pos] = NULLCHAR;
2930 // try to be smart: if it does not look like search info, it should go to
2931 // ICS interaction window after all, not to engine-output window.
2932 for(j=0; j<parse_pos; j++) { // count letters and digits
2933 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2934 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2935 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2937 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2938 int depth=0; float score;
2939 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2940 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2941 pvInfoList[forwardMostMove-1].depth = depth;
2942 pvInfoList[forwardMostMove-1].score = 100*score;
2944 OutputKibitz(suppressKibitz, parse);
2947 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2948 SendToPlayer(tmp, strlen(tmp));
2950 next_out = i+1; // [HGM] suppress printing in ICS window
2952 started = STARTED_NONE;
2954 /* Don't match patterns against characters in comment */
2959 if (started == STARTED_CHATTER) {
2960 if (buf[i] != '\n') {
2961 /* Don't match patterns against characters in chatter */
2965 started = STARTED_NONE;
2966 if(suppressKibitz) next_out = i+1;
2969 /* Kludge to deal with rcmd protocol */
2970 if (firstTime && looking_at(buf, &i, "\001*")) {
2971 DisplayFatalError(&buf[1], 0, 1);
2977 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2980 if (appData.debugMode)
2981 fprintf(debugFP, "ics_type %d\n", ics_type);
2984 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2985 ics_type = ICS_FICS;
2987 if (appData.debugMode)
2988 fprintf(debugFP, "ics_type %d\n", ics_type);
2991 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2992 ics_type = ICS_CHESSNET;
2994 if (appData.debugMode)
2995 fprintf(debugFP, "ics_type %d\n", ics_type);
3000 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3001 looking_at(buf, &i, "Logging you in as \"*\"") ||
3002 looking_at(buf, &i, "will be \"*\""))) {
3003 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3007 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3009 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3010 DisplayIcsInteractionTitle(buf);
3011 have_set_title = TRUE;
3014 /* skip finger notes */
3015 if (started == STARTED_NONE &&
3016 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3017 (buf[i] == '1' && buf[i+1] == '0')) &&
3018 buf[i+2] == ':' && buf[i+3] == ' ') {
3019 started = STARTED_CHATTER;
3025 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3026 if(appData.seekGraph) {
3027 if(soughtPending && MatchSoughtLine(buf+i)) {
3028 i = strstr(buf+i, "rated") - buf;
3029 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030 next_out = leftover_start = i;
3031 started = STARTED_CHATTER;
3032 suppressKibitz = TRUE;
3035 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3036 && looking_at(buf, &i, "* ads displayed")) {
3037 soughtPending = FALSE;
3042 if(appData.autoRefresh) {
3043 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3044 int s = (ics_type == ICS_ICC); // ICC format differs
3046 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3047 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3048 looking_at(buf, &i, "*% "); // eat prompt
3049 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3050 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051 next_out = i; // suppress
3054 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3055 char *p = star_match[0];
3057 if(seekGraphUp) RemoveSeekAd(atoi(p));
3058 while(*p && *p++ != ' '); // next
3060 looking_at(buf, &i, "*% "); // eat prompt
3061 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068 /* skip formula vars */
3069 if (started == STARTED_NONE &&
3070 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3071 started = STARTED_CHATTER;
3076 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3077 if (appData.autoKibitz && started == STARTED_NONE &&
3078 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3079 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3080 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3081 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3082 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3083 suppressKibitz = TRUE;
3084 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3087 && (gameMode == IcsPlayingWhite)) ||
3088 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3089 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3090 started = STARTED_CHATTER; // own kibitz we simply discard
3092 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3093 parse_pos = 0; parse[0] = NULLCHAR;
3094 savingComment = TRUE;
3095 suppressKibitz = gameMode != IcsObserving ? 2 :
3096 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3100 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3101 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3102 && atoi(star_match[0])) {
3103 // suppress the acknowledgements of our own autoKibitz
3105 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3107 SendToPlayer(star_match[0], strlen(star_match[0]));
3108 if(looking_at(buf, &i, "*% ")) // eat prompt
3109 suppressKibitz = FALSE;
3113 } // [HGM] kibitz: end of patch
3115 // [HGM] chat: intercept tells by users for which we have an open chat window
3117 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3118 looking_at(buf, &i, "* whispers:") ||
3119 looking_at(buf, &i, "* kibitzes:") ||
3120 looking_at(buf, &i, "* shouts:") ||
3121 looking_at(buf, &i, "* c-shouts:") ||
3122 looking_at(buf, &i, "--> * ") ||
3123 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3124 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3125 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3126 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3128 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3129 chattingPartner = -1;
3131 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3132 for(p=0; p<MAX_CHAT; p++) {
3133 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3134 talker[0] = '['; strcat(talker, "] ");
3135 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3136 chattingPartner = p; break;
3139 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3140 for(p=0; p<MAX_CHAT; p++) {
3141 if(!strcmp("kibitzes", chatPartner[p])) {
3142 talker[0] = '['; strcat(talker, "] ");
3143 chattingPartner = p; break;
3146 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3147 for(p=0; p<MAX_CHAT; p++) {
3148 if(!strcmp("whispers", chatPartner[p])) {
3149 talker[0] = '['; strcat(talker, "] ");
3150 chattingPartner = p; break;
3153 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3154 if(buf[i-8] == '-' && buf[i-3] == 't')
3155 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3156 if(!strcmp("c-shouts", chatPartner[p])) {
3157 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3158 chattingPartner = p; break;
3161 if(chattingPartner < 0)
3162 for(p=0; p<MAX_CHAT; p++) {
3163 if(!strcmp("shouts", chatPartner[p])) {
3164 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3165 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3166 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3167 chattingPartner = p; break;
3171 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3172 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3173 talker[0] = 0; Colorize(ColorTell, FALSE);
3174 chattingPartner = p; break;
3176 if(chattingPartner<0) i = oldi; else {
3177 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3178 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3179 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3180 started = STARTED_COMMENT;
3181 parse_pos = 0; parse[0] = NULLCHAR;
3182 savingComment = 3 + chattingPartner; // counts as TRUE
3183 suppressKibitz = TRUE;
3186 } // [HGM] chat: end of patch
3189 if (appData.zippyTalk || appData.zippyPlay) {
3190 /* [DM] Backup address for color zippy lines */
3192 if (loggedOn == TRUE)
3193 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3194 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3196 } // [DM] 'else { ' deleted
3198 /* Regular tells and says */
3199 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3200 looking_at(buf, &i, "* (your partner) tells you: ") ||
3201 looking_at(buf, &i, "* says: ") ||
3202 /* Don't color "message" or "messages" output */
3203 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3204 looking_at(buf, &i, "*. * at *:*: ") ||
3205 looking_at(buf, &i, "--* (*:*): ") ||
3206 /* Message notifications (same color as tells) */
3207 looking_at(buf, &i, "* has left a message ") ||
3208 looking_at(buf, &i, "* just sent you a message:\n") ||
3209 /* Whispers and kibitzes */
3210 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3211 looking_at(buf, &i, "* kibitzes: ") ||
3213 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3215 if (tkind == 1 && strchr(star_match[0], ':')) {
3216 /* Avoid "tells you:" spoofs in channels */
3219 if (star_match[0][0] == NULLCHAR ||
3220 strchr(star_match[0], ' ') ||
3221 (tkind == 3 && strchr(star_match[1], ' '))) {
3222 /* Reject bogus matches */
3225 if (appData.colorize) {
3226 if (oldi > next_out) {
3227 SendToPlayer(&buf[next_out], oldi - next_out);
3232 Colorize(ColorTell, FALSE);
3233 curColor = ColorTell;
3236 Colorize(ColorKibitz, FALSE);
3237 curColor = ColorKibitz;
3240 p = strrchr(star_match[1], '(');
3247 Colorize(ColorChannel1, FALSE);
3248 curColor = ColorChannel1;
3250 Colorize(ColorChannel, FALSE);
3251 curColor = ColorChannel;
3255 curColor = ColorNormal;
3259 if (started == STARTED_NONE && appData.autoComment &&
3260 (gameMode == IcsObserving ||
3261 gameMode == IcsPlayingWhite ||
3262 gameMode == IcsPlayingBlack)) {
3263 parse_pos = i - oldi;
3264 memcpy(parse, &buf[oldi], parse_pos);
3265 parse[parse_pos] = NULLCHAR;
3266 started = STARTED_COMMENT;
3267 savingComment = TRUE;
3269 started = STARTED_CHATTER;
3270 savingComment = FALSE;
3277 if (looking_at(buf, &i, "* s-shouts: ") ||
3278 looking_at(buf, &i, "* c-shouts: ")) {
3279 if (appData.colorize) {
3280 if (oldi > next_out) {
3281 SendToPlayer(&buf[next_out], oldi - next_out);
3284 Colorize(ColorSShout, FALSE);
3285 curColor = ColorSShout;
3288 started = STARTED_CHATTER;
3292 if (looking_at(buf, &i, "--->")) {
3297 if (looking_at(buf, &i, "* shouts: ") ||
3298 looking_at(buf, &i, "--> ")) {
3299 if (appData.colorize) {
3300 if (oldi > next_out) {
3301 SendToPlayer(&buf[next_out], oldi - next_out);
3304 Colorize(ColorShout, FALSE);
3305 curColor = ColorShout;
3308 started = STARTED_CHATTER;
3312 if (looking_at( buf, &i, "Challenge:")) {
3313 if (appData.colorize) {
3314 if (oldi > next_out) {
3315 SendToPlayer(&buf[next_out], oldi - next_out);
3318 Colorize(ColorChallenge, FALSE);
3319 curColor = ColorChallenge;
3325 if (looking_at(buf, &i, "* offers you") ||
3326 looking_at(buf, &i, "* offers to be") ||
3327 looking_at(buf, &i, "* would like to") ||
3328 looking_at(buf, &i, "* requests to") ||
3329 looking_at(buf, &i, "Your opponent offers") ||
3330 looking_at(buf, &i, "Your opponent requests")) {
3332 if (appData.colorize) {
3333 if (oldi > next_out) {
3334 SendToPlayer(&buf[next_out], oldi - next_out);
3337 Colorize(ColorRequest, FALSE);
3338 curColor = ColorRequest;
3343 if (looking_at(buf, &i, "* (*) seeking")) {
3344 if (appData.colorize) {
3345 if (oldi > next_out) {
3346 SendToPlayer(&buf[next_out], oldi - next_out);
3349 Colorize(ColorSeek, FALSE);
3350 curColor = ColorSeek;
3355 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3357 if (looking_at(buf, &i, "\\ ")) {
3358 if (prevColor != ColorNormal) {
3359 if (oldi > next_out) {
3360 SendToPlayer(&buf[next_out], oldi - next_out);
3363 Colorize(prevColor, TRUE);
3364 curColor = prevColor;
3366 if (savingComment) {
3367 parse_pos = i - oldi;
3368 memcpy(parse, &buf[oldi], parse_pos);
3369 parse[parse_pos] = NULLCHAR;
3370 started = STARTED_COMMENT;
3371 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3372 chattingPartner = savingComment - 3; // kludge to remember the box
3374 started = STARTED_CHATTER;
3379 if (looking_at(buf, &i, "Black Strength :") ||
3380 looking_at(buf, &i, "<<< style 10 board >>>") ||
3381 looking_at(buf, &i, "<10>") ||
3382 looking_at(buf, &i, "#@#")) {
3383 /* Wrong board style */
3385 SendToICS(ics_prefix);
3386 SendToICS("set style 12\n");
3387 SendToICS(ics_prefix);
3388 SendToICS("refresh\n");
3392 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3394 have_sent_ICS_logon = 1;
3398 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3399 (looking_at(buf, &i, "\n<12> ") ||
3400 looking_at(buf, &i, "<12> "))) {
3402 if (oldi > next_out) {
3403 SendToPlayer(&buf[next_out], oldi - next_out);
3406 started = STARTED_BOARD;
3411 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3412 looking_at(buf, &i, "<b1> ")) {
3413 if (oldi > next_out) {
3414 SendToPlayer(&buf[next_out], oldi - next_out);
3417 started = STARTED_HOLDINGS;
3422 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3424 /* Header for a move list -- first line */
3426 switch (ics_getting_history) {
3430 case BeginningOfGame:
3431 /* User typed "moves" or "oldmoves" while we
3432 were idle. Pretend we asked for these
3433 moves and soak them up so user can step
3434 through them and/or save them.
3437 gameMode = IcsObserving;
3440 ics_getting_history = H_GOT_UNREQ_HEADER;
3442 case EditGame: /*?*/
3443 case EditPosition: /*?*/
3444 /* Should above feature work in these modes too? */
3445 /* For now it doesn't */
3446 ics_getting_history = H_GOT_UNWANTED_HEADER;
3449 ics_getting_history = H_GOT_UNWANTED_HEADER;
3454 /* Is this the right one? */
3455 if (gameInfo.white && gameInfo.black &&
3456 strcmp(gameInfo.white, star_match[0]) == 0 &&
3457 strcmp(gameInfo.black, star_match[2]) == 0) {
3459 ics_getting_history = H_GOT_REQ_HEADER;
3462 case H_GOT_REQ_HEADER:
3463 case H_GOT_UNREQ_HEADER:
3464 case H_GOT_UNWANTED_HEADER:
3465 case H_GETTING_MOVES:
3466 /* Should not happen */
3467 DisplayError(_("Error gathering move list: two headers"), 0);
3468 ics_getting_history = H_FALSE;
3472 /* Save player ratings into gameInfo if needed */
3473 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3474 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3475 (gameInfo.whiteRating == -1 ||
3476 gameInfo.blackRating == -1)) {
3478 gameInfo.whiteRating = string_to_rating(star_match[1]);
3479 gameInfo.blackRating = string_to_rating(star_match[3]);
3480 if (appData.debugMode)
3481 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3482 gameInfo.whiteRating, gameInfo.blackRating);
3487 if (looking_at(buf, &i,
3488 "* * match, initial time: * minute*, increment: * second")) {
3489 /* Header for a move list -- second line */
3490 /* Initial board will follow if this is a wild game */
3491 if (gameInfo.event != NULL) free(gameInfo.event);
3492 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3493 gameInfo.event = StrSave(str);
3494 /* [HGM] we switched variant. Translate boards if needed. */
3495 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3499 if (looking_at(buf, &i, "Move ")) {
3500 /* Beginning of a move list */
3501 switch (ics_getting_history) {
3503 /* Normally should not happen */
3504 /* Maybe user hit reset while we were parsing */
3507 /* Happens if we are ignoring a move list that is not
3508 * the one we just requested. Common if the user
3509 * tries to observe two games without turning off
3512 case H_GETTING_MOVES:
3513 /* Should not happen */
3514 DisplayError(_("Error gathering move list: nested"), 0);
3515 ics_getting_history = H_FALSE;
3517 case H_GOT_REQ_HEADER:
3518 ics_getting_history = H_GETTING_MOVES;
3519 started = STARTED_MOVES;
3521 if (oldi > next_out) {
3522 SendToPlayer(&buf[next_out], oldi - next_out);
3525 case H_GOT_UNREQ_HEADER:
3526 ics_getting_history = H_GETTING_MOVES;
3527 started = STARTED_MOVES_NOHIDE;
3530 case H_GOT_UNWANTED_HEADER:
3531 ics_getting_history = H_FALSE;
3537 if (looking_at(buf, &i, "% ") ||
3538 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3539 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3540 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3541 soughtPending = FALSE;
3545 if(suppressKibitz) next_out = i;
3546 savingComment = FALSE;
3550 case STARTED_MOVES_NOHIDE:
3551 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3552 parse[parse_pos + i - oldi] = NULLCHAR;
3553 ParseGameHistory(parse);
3555 if (appData.zippyPlay && first.initDone) {
3556 FeedMovesToProgram(&first, forwardMostMove);
3557 if (gameMode == IcsPlayingWhite) {
3558 if (WhiteOnMove(forwardMostMove)) {
3559 if (first.sendTime) {
3560 if (first.useColors) {
3561 SendToProgram("black\n", &first);
3563 SendTimeRemaining(&first, TRUE);
3565 if (first.useColors) {
3566 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3568 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3569 first.maybeThinking = TRUE;
3571 if (first.usePlayother) {
3572 if (first.sendTime) {
3573 SendTimeRemaining(&first, TRUE);
3575 SendToProgram("playother\n", &first);
3581 } else if (gameMode == IcsPlayingBlack) {
3582 if (!WhiteOnMove(forwardMostMove)) {
3583 if (first.sendTime) {
3584 if (first.useColors) {
3585 SendToProgram("white\n", &first);
3587 SendTimeRemaining(&first, FALSE);
3589 if (first.useColors) {
3590 SendToProgram("black\n", &first);
3592 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3593 first.maybeThinking = TRUE;
3595 if (first.usePlayother) {
3596 if (first.sendTime) {
3597 SendTimeRemaining(&first, FALSE);
3599 SendToProgram("playother\n", &first);
3608 if (gameMode == IcsObserving && ics_gamenum == -1) {
3609 /* Moves came from oldmoves or moves command
3610 while we weren't doing anything else.
3612 currentMove = forwardMostMove;
3613 ClearHighlights();/*!!could figure this out*/
3614 flipView = appData.flipView;
3615 DrawPosition(TRUE, boards[currentMove]);
3616 DisplayBothClocks();
3617 snprintf(str, MSG_SIZ, "%s vs. %s",
3618 gameInfo.white, gameInfo.black);
3622 /* Moves were history of an active game */
3623 if (gameInfo.resultDetails != NULL) {
3624 free(gameInfo.resultDetails);
3625 gameInfo.resultDetails = NULL;
3628 HistorySet(parseList, backwardMostMove,
3629 forwardMostMove, currentMove-1);
3630 DisplayMove(currentMove - 1);
3631 if (started == STARTED_MOVES) next_out = i;
3632 started = STARTED_NONE;
3633 ics_getting_history = H_FALSE;
3636 case STARTED_OBSERVE:
3637 started = STARTED_NONE;
3638 SendToICS(ics_prefix);
3639 SendToICS("refresh\n");
3645 if(bookHit) { // [HGM] book: simulate book reply
3646 static char bookMove[MSG_SIZ]; // a bit generous?
3648 programStats.nodes = programStats.depth = programStats.time =
3649 programStats.score = programStats.got_only_move = 0;
3650 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3652 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3653 strcat(bookMove, bookHit);
3654 HandleMachineMove(bookMove, &first);
3659 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3660 started == STARTED_HOLDINGS ||
3661 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3662 /* Accumulate characters in move list or board */
3663 parse[parse_pos++] = buf[i];
3666 /* Start of game messages. Mostly we detect start of game
3667 when the first board image arrives. On some versions
3668 of the ICS, though, we need to do a "refresh" after starting
3669 to observe in order to get the current board right away. */
3670 if (looking_at(buf, &i, "Adding game * to observation list")) {
3671 started = STARTED_OBSERVE;
3675 /* Handle auto-observe */
3676 if (appData.autoObserve &&
3677 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3678 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3680 /* Choose the player that was highlighted, if any. */
3681 if (star_match[0][0] == '\033' ||
3682 star_match[1][0] != '\033') {
3683 player = star_match[0];
3685 player = star_match[2];
3687 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3688 ics_prefix, StripHighlightAndTitle(player));
3691 /* Save ratings from notify string */
3692 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3693 player1Rating = string_to_rating(star_match[1]);
3694 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3695 player2Rating = string_to_rating(star_match[3]);
3697 if (appData.debugMode)
3699 "Ratings from 'Game notification:' %s %d, %s %d\n",
3700 player1Name, player1Rating,
3701 player2Name, player2Rating);
3706 /* Deal with automatic examine mode after a game,
3707 and with IcsObserving -> IcsExamining transition */
3708 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3709 looking_at(buf, &i, "has made you an examiner of game *")) {
3711 int gamenum = atoi(star_match[0]);
3712 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3713 gamenum == ics_gamenum) {
3714 /* We were already playing or observing this game;
3715 no need to refetch history */
3716 gameMode = IcsExamining;
3718 pauseExamForwardMostMove = forwardMostMove;
3719 } else if (currentMove < forwardMostMove) {
3720 ForwardInner(forwardMostMove);
3723 /* I don't think this case really can happen */
3724 SendToICS(ics_prefix);
3725 SendToICS("refresh\n");
3730 /* Error messages */
3731 // if (ics_user_moved) {
3732 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3733 if (looking_at(buf, &i, "Illegal move") ||
3734 looking_at(buf, &i, "Not a legal move") ||
3735 looking_at(buf, &i, "Your king is in check") ||
3736 looking_at(buf, &i, "It isn't your turn") ||
3737 looking_at(buf, &i, "It is not your move")) {
3739 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3740 currentMove = forwardMostMove-1;
3741 DisplayMove(currentMove - 1); /* before DMError */
3742 DrawPosition(FALSE, boards[currentMove]);
3743 SwitchClocks(forwardMostMove-1); // [HGM] race
3744 DisplayBothClocks();
3746 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3752 if (looking_at(buf, &i, "still have time") ||
3753 looking_at(buf, &i, "not out of time") ||
3754 looking_at(buf, &i, "either player is out of time") ||
3755 looking_at(buf, &i, "has timeseal; checking")) {
3756 /* We must have called his flag a little too soon */
3757 whiteFlag = blackFlag = FALSE;
3761 if (looking_at(buf, &i, "added * seconds to") ||
3762 looking_at(buf, &i, "seconds were added to")) {
3763 /* Update the clocks */
3764 SendToICS(ics_prefix);
3765 SendToICS("refresh\n");
3769 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3770 ics_clock_paused = TRUE;
3775 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3776 ics_clock_paused = FALSE;
3781 /* Grab player ratings from the Creating: message.
3782 Note we have to check for the special case when
3783 the ICS inserts things like [white] or [black]. */
3784 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3785 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3787 0 player 1 name (not necessarily white)
3789 2 empty, white, or black (IGNORED)
3790 3 player 2 name (not necessarily black)
3793 The names/ratings are sorted out when the game
3794 actually starts (below).
3796 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3797 player1Rating = string_to_rating(star_match[1]);
3798 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3799 player2Rating = string_to_rating(star_match[4]);
3801 if (appData.debugMode)
3803 "Ratings from 'Creating:' %s %d, %s %d\n",
3804 player1Name, player1Rating,
3805 player2Name, player2Rating);
3810 /* Improved generic start/end-of-game messages */
3811 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3812 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3813 /* If tkind == 0: */
3814 /* star_match[0] is the game number */
3815 /* [1] is the white player's name */
3816 /* [2] is the black player's name */
3817 /* For end-of-game: */
3818 /* [3] is the reason for the game end */
3819 /* [4] is a PGN end game-token, preceded by " " */
3820 /* For start-of-game: */
3821 /* [3] begins with "Creating" or "Continuing" */
3822 /* [4] is " *" or empty (don't care). */
3823 int gamenum = atoi(star_match[0]);
3824 char *whitename, *blackname, *why, *endtoken;
3825 ChessMove endtype = EndOfFile;
3828 whitename = star_match[1];
3829 blackname = star_match[2];
3830 why = star_match[3];
3831 endtoken = star_match[4];
3833 whitename = star_match[1];
3834 blackname = star_match[3];
3835 why = star_match[5];
3836 endtoken = star_match[6];
3839 /* Game start messages */
3840 if (strncmp(why, "Creating ", 9) == 0 ||
3841 strncmp(why, "Continuing ", 11) == 0) {
3842 gs_gamenum = gamenum;
3843 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3844 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3846 if (appData.zippyPlay) {
3847 ZippyGameStart(whitename, blackname);
3850 partnerBoardValid = FALSE; // [HGM] bughouse
3854 /* Game end messages */
3855 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3856 ics_gamenum != gamenum) {
3859 while (endtoken[0] == ' ') endtoken++;
3860 switch (endtoken[0]) {
3863 endtype = GameUnfinished;
3866 endtype = BlackWins;
3869 if (endtoken[1] == '/')
3870 endtype = GameIsDrawn;
3872 endtype = WhiteWins;
3875 GameEnds(endtype, why, GE_ICS);
3877 if (appData.zippyPlay && first.initDone) {
3878 ZippyGameEnd(endtype, why);
3879 if (first.pr == NULL) {
3880 /* Start the next process early so that we'll
3881 be ready for the next challenge */
3882 StartChessProgram(&first);
3884 /* Send "new" early, in case this command takes
3885 a long time to finish, so that we'll be ready
3886 for the next challenge. */
3887 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3891 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3895 if (looking_at(buf, &i, "Removing game * from observation") ||
3896 looking_at(buf, &i, "no longer observing game *") ||
3897 looking_at(buf, &i, "Game * (*) has no examiners")) {
3898 if (gameMode == IcsObserving &&
3899 atoi(star_match[0]) == ics_gamenum)
3901 /* icsEngineAnalyze */
3902 if (appData.icsEngineAnalyze) {
3909 ics_user_moved = FALSE;
3914 if (looking_at(buf, &i, "no longer examining game *")) {
3915 if (gameMode == IcsExamining &&
3916 atoi(star_match[0]) == ics_gamenum)
3920 ics_user_moved = FALSE;
3925 /* Advance leftover_start past any newlines we find,
3926 so only partial lines can get reparsed */
3927 if (looking_at(buf, &i, "\n")) {
3928 prevColor = curColor;
3929 if (curColor != ColorNormal) {
3930 if (oldi > next_out) {
3931 SendToPlayer(&buf[next_out], oldi - next_out);
3934 Colorize(ColorNormal, FALSE);
3935 curColor = ColorNormal;
3937 if (started == STARTED_BOARD) {
3938 started = STARTED_NONE;
3939 parse[parse_pos] = NULLCHAR;
3940 ParseBoard12(parse);
3943 /* Send premove here */
3944 if (appData.premove) {
3946 if (currentMove == 0 &&
3947 gameMode == IcsPlayingWhite &&
3948 appData.premoveWhite) {
3949 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3950 if (appData.debugMode)
3951 fprintf(debugFP, "Sending premove:\n");
3953 } else if (currentMove == 1 &&
3954 gameMode == IcsPlayingBlack &&
3955 appData.premoveBlack) {
3956 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3957 if (appData.debugMode)
3958 fprintf(debugFP, "Sending premove:\n");
3960 } else if (gotPremove) {
3962 ClearPremoveHighlights();
3963 if (appData.debugMode)
3964 fprintf(debugFP, "Sending premove:\n");
3965 UserMoveEvent(premoveFromX, premoveFromY,
3966 premoveToX, premoveToY,
3971 /* Usually suppress following prompt */
3972 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3973 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3974 if (looking_at(buf, &i, "*% ")) {
3975 savingComment = FALSE;
3980 } else if (started == STARTED_HOLDINGS) {
3982 char new_piece[MSG_SIZ];
3983 started = STARTED_NONE;
3984 parse[parse_pos] = NULLCHAR;
3985 if (appData.debugMode)
3986 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3987 parse, currentMove);
3988 if (sscanf(parse, " game %d", &gamenum) == 1) {
3989 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3990 if (gameInfo.variant == VariantNormal) {
3991 /* [HGM] We seem to switch variant during a game!
3992 * Presumably no holdings were displayed, so we have
3993 * to move the position two files to the right to
3994 * create room for them!
3996 VariantClass newVariant;
3997 switch(gameInfo.boardWidth) { // base guess on board width
3998 case 9: newVariant = VariantShogi; break;
3999 case 10: newVariant = VariantGreat; break;
4000 default: newVariant = VariantCrazyhouse; break;
4002 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4003 /* Get a move list just to see the header, which
4004 will tell us whether this is really bug or zh */
4005 if (ics_getting_history == H_FALSE) {
4006 ics_getting_history = H_REQUESTED;
4007 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4011 new_piece[0] = NULLCHAR;
4012 sscanf(parse, "game %d white [%s black [%s <- %s",
4013 &gamenum, white_holding, black_holding,
4015 white_holding[strlen(white_holding)-1] = NULLCHAR;
4016 black_holding[strlen(black_holding)-1] = NULLCHAR;
4017 /* [HGM] copy holdings to board holdings area */
4018 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4019 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4020 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4022 if (appData.zippyPlay && first.initDone) {
4023 ZippyHoldings(white_holding, black_holding,
4027 if (tinyLayout || smallLayout) {
4028 char wh[16], bh[16];
4029 PackHolding(wh, white_holding);
4030 PackHolding(bh, black_holding);
4031 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4032 gameInfo.white, gameInfo.black);
4034 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4035 gameInfo.white, white_holding,
4036 gameInfo.black, black_holding);
4038 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4039 DrawPosition(FALSE, boards[currentMove]);
4041 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4042 sscanf(parse, "game %d white [%s black [%s <- %s",
4043 &gamenum, white_holding, black_holding,
4045 white_holding[strlen(white_holding)-1] = NULLCHAR;
4046 black_holding[strlen(black_holding)-1] = NULLCHAR;
4047 /* [HGM] copy holdings to partner-board holdings area */
4048 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4049 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4050 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4051 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4052 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4055 /* Suppress following prompt */
4056 if (looking_at(buf, &i, "*% ")) {
4057 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4058 savingComment = FALSE;
4066 i++; /* skip unparsed character and loop back */
4069 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4070 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4071 // SendToPlayer(&buf[next_out], i - next_out);
4072 started != STARTED_HOLDINGS && leftover_start > next_out) {
4073 SendToPlayer(&buf[next_out], leftover_start - next_out);
4077 leftover_len = buf_len - leftover_start;
4078 /* if buffer ends with something we couldn't parse,
4079 reparse it after appending the next read */
4081 } else if (count == 0) {
4082 RemoveInputSource(isr);
4083 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4085 DisplayFatalError(_("Error reading from ICS"), error, 1);
4090 /* Board style 12 looks like this:
4092 <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
4094 * The "<12> " is stripped before it gets to this routine. The two
4095 * trailing 0's (flip state and clock ticking) are later addition, and
4096 * some chess servers may not have them, or may have only the first.
4097 * Additional trailing fields may be added in the future.
4100 #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"
4102 #define RELATION_OBSERVING_PLAYED 0
4103 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4104 #define RELATION_PLAYING_MYMOVE 1
4105 #define RELATION_PLAYING_NOTMYMOVE -1
4106 #define RELATION_EXAMINING 2
4107 #define RELATION_ISOLATED_BOARD -3
4108 #define RELATION_STARTING_POSITION -4 /* FICS only */
4111 ParseBoard12(string)
4114 GameMode newGameMode;
4115 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4116 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4117 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4118 char to_play, board_chars[200];
4119 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4120 char black[32], white[32];
4122 int prevMove = currentMove;
4125 int fromX, fromY, toX, toY;
4127 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4128 char *bookHit = NULL; // [HGM] book
4129 Boolean weird = FALSE, reqFlag = FALSE;
4131 fromX = fromY = toX = toY = -1;
4135 if (appData.debugMode)
4136 fprintf(debugFP, _("Parsing board: %s\n"), string);
4138 move_str[0] = NULLCHAR;
4139 elapsed_time[0] = NULLCHAR;
4140 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4142 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4143 if(string[i] == ' ') { ranks++; files = 0; }
4145 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4148 for(j = 0; j <i; j++) board_chars[j] = string[j];
4149 board_chars[i] = '\0';
4152 n = sscanf(string, PATTERN, &to_play, &double_push,
4153 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4154 &gamenum, white, black, &relation, &basetime, &increment,
4155 &white_stren, &black_stren, &white_time, &black_time,
4156 &moveNum, str, elapsed_time, move_str, &ics_flip,
4160 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4161 DisplayError(str, 0);
4165 /* Convert the move number to internal form */
4166 moveNum = (moveNum - 1) * 2;
4167 if (to_play == 'B') moveNum++;
4168 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4169 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4175 case RELATION_OBSERVING_PLAYED:
4176 case RELATION_OBSERVING_STATIC:
4177 if (gamenum == -1) {
4178 /* Old ICC buglet */
4179 relation = RELATION_OBSERVING_STATIC;
4181 newGameMode = IcsObserving;
4183 case RELATION_PLAYING_MYMOVE:
4184 case RELATION_PLAYING_NOTMYMOVE:
4186 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4187 IcsPlayingWhite : IcsPlayingBlack;
4189 case RELATION_EXAMINING:
4190 newGameMode = IcsExamining;
4192 case RELATION_ISOLATED_BOARD:
4194 /* Just display this board. If user was doing something else,
4195 we will forget about it until the next board comes. */
4196 newGameMode = IcsIdle;
4198 case RELATION_STARTING_POSITION:
4199 newGameMode = gameMode;
4203 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4204 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4205 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4207 for (k = 0; k < ranks; k++) {
4208 for (j = 0; j < files; j++)
4209 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4210 if(gameInfo.holdingsWidth > 1) {
4211 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4212 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215 CopyBoard(partnerBoard, board);
4216 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4217 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4218 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4219 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4220 if(toSqr = strchr(str, '-')) {
4221 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4222 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4223 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4224 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4225 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4226 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4227 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4228 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4229 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4230 DisplayMessage(partnerStatus, "");
4231 partnerBoardValid = TRUE;
4235 /* Modify behavior for initial board display on move listing
4238 switch (ics_getting_history) {
4242 case H_GOT_REQ_HEADER:
4243 case H_GOT_UNREQ_HEADER:
4244 /* This is the initial position of the current game */
4245 gamenum = ics_gamenum;
4246 moveNum = 0; /* old ICS bug workaround */
4247 if (to_play == 'B') {
4248 startedFromSetupPosition = TRUE;
4249 blackPlaysFirst = TRUE;
4251 if (forwardMostMove == 0) forwardMostMove = 1;
4252 if (backwardMostMove == 0) backwardMostMove = 1;
4253 if (currentMove == 0) currentMove = 1;
4255 newGameMode = gameMode;
4256 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4258 case H_GOT_UNWANTED_HEADER:
4259 /* This is an initial board that we don't want */
4261 case H_GETTING_MOVES:
4262 /* Should not happen */
4263 DisplayError(_("Error gathering move list: extra board"), 0);
4264 ics_getting_history = H_FALSE;
4268 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4269 weird && (int)gameInfo.variant < (int)VariantShogi) {
4270 /* [HGM] We seem to have switched variant unexpectedly
4271 * Try to guess new variant from board size
4273 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4274 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4275 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4276 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4277 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4278 if(!weird) newVariant = VariantNormal;
4279 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4280 /* Get a move list just to see the header, which
4281 will tell us whether this is really bug or zh */
4282 if (ics_getting_history == H_FALSE) {
4283 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4284 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4289 /* Take action if this is the first board of a new game, or of a
4290 different game than is currently being displayed. */
4291 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4292 relation == RELATION_ISOLATED_BOARD) {
4294 /* Forget the old game and get the history (if any) of the new one */
4295 if (gameMode != BeginningOfGame) {
4299 if (appData.autoRaiseBoard) BoardToTop();
4301 if (gamenum == -1) {
4302 newGameMode = IcsIdle;
4303 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4304 appData.getMoveList && !reqFlag) {
4305 /* Need to get game history */
4306 ics_getting_history = H_REQUESTED;
4307 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4311 /* Initially flip the board to have black on the bottom if playing
4312 black or if the ICS flip flag is set, but let the user change
4313 it with the Flip View button. */
4314 flipView = appData.autoFlipView ?
4315 (newGameMode == IcsPlayingBlack) || ics_flip :
4318 /* Done with values from previous mode; copy in new ones */
4319 gameMode = newGameMode;
4321 ics_gamenum = gamenum;
4322 if (gamenum == gs_gamenum) {
4323 int klen = strlen(gs_kind);
4324 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4325 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4326 gameInfo.event = StrSave(str);
4328 gameInfo.event = StrSave("ICS game");
4330 gameInfo.site = StrSave(appData.icsHost);
4331 gameInfo.date = PGNDate();
4332 gameInfo.round = StrSave("-");
4333 gameInfo.white = StrSave(white);
4334 gameInfo.black = StrSave(black);
4335 timeControl = basetime * 60 * 1000;
4337 timeIncrement = increment * 1000;
4338 movesPerSession = 0;
4339 gameInfo.timeControl = TimeControlTagValue();
4340 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4341 if (appData.debugMode) {
4342 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4343 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4344 setbuf(debugFP, NULL);
4347 gameInfo.outOfBook = NULL;
4349 /* Do we have the ratings? */
4350 if (strcmp(player1Name, white) == 0 &&
4351 strcmp(player2Name, black) == 0) {
4352 if (appData.debugMode)
4353 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4354 player1Rating, player2Rating);
4355 gameInfo.whiteRating = player1Rating;
4356 gameInfo.blackRating = player2Rating;
4357 } else if (strcmp(player2Name, white) == 0 &&
4358 strcmp(player1Name, black) == 0) {
4359 if (appData.debugMode)
4360 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4361 player2Rating, player1Rating);
4362 gameInfo.whiteRating = player2Rating;
4363 gameInfo.blackRating = player1Rating;
4365 player1Name[0] = player2Name[0] = NULLCHAR;
4367 /* Silence shouts if requested */
4368 if (appData.quietPlay &&
4369 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4370 SendToICS(ics_prefix);
4371 SendToICS("set shout 0\n");
4375 /* Deal with midgame name changes */
4377 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4378 if (gameInfo.white) free(gameInfo.white);
4379 gameInfo.white = StrSave(white);
4381 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4382 if (gameInfo.black) free(gameInfo.black);
4383 gameInfo.black = StrSave(black);
4387 /* Throw away game result if anything actually changes in examine mode */
4388 if (gameMode == IcsExamining && !newGame) {
4389 gameInfo.result = GameUnfinished;
4390 if (gameInfo.resultDetails != NULL) {
4391 free(gameInfo.resultDetails);
4392 gameInfo.resultDetails = NULL;
4396 /* In pausing && IcsExamining mode, we ignore boards coming
4397 in if they are in a different variation than we are. */
4398 if (pauseExamInvalid) return;
4399 if (pausing && gameMode == IcsExamining) {
4400 if (moveNum <= pauseExamForwardMostMove) {
4401 pauseExamInvalid = TRUE;
4402 forwardMostMove = pauseExamForwardMostMove;
4407 if (appData.debugMode) {
4408 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4410 /* Parse the board */
4411 for (k = 0; k < ranks; k++) {
4412 for (j = 0; j < files; j++)
4413 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4414 if(gameInfo.holdingsWidth > 1) {
4415 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4416 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4419 CopyBoard(boards[moveNum], board);
4420 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4422 startedFromSetupPosition =
4423 !CompareBoards(board, initialPosition);
4424 if(startedFromSetupPosition)
4425 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4428 /* [HGM] Set castling rights. Take the outermost Rooks,
4429 to make it also work for FRC opening positions. Note that board12
4430 is really defective for later FRC positions, as it has no way to
4431 indicate which Rook can castle if they are on the same side of King.
4432 For the initial position we grant rights to the outermost Rooks,
4433 and remember thos rights, and we then copy them on positions
4434 later in an FRC game. This means WB might not recognize castlings with
4435 Rooks that have moved back to their original position as illegal,
4436 but in ICS mode that is not its job anyway.
4438 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4439 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4441 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442 if(board[0][i] == WhiteRook) j = i;
4443 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445 if(board[0][i] == WhiteRook) j = i;
4446 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4448 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4451 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4452 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4455 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4457 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458 if(board[BOARD_HEIGHT-1][k] == bKing)
4459 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4460 if(gameInfo.variant == VariantTwoKings) {
4461 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4462 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4463 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4466 r = boards[moveNum][CASTLING][0] = initialRights[0];
4467 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4468 r = boards[moveNum][CASTLING][1] = initialRights[1];
4469 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4470 r = boards[moveNum][CASTLING][3] = initialRights[3];
4471 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4472 r = boards[moveNum][CASTLING][4] = initialRights[4];
4473 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4474 /* wildcastle kludge: always assume King has rights */
4475 r = boards[moveNum][CASTLING][2] = initialRights[2];
4476 r = boards[moveNum][CASTLING][5] = initialRights[5];
4478 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4479 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4482 if (ics_getting_history == H_GOT_REQ_HEADER ||
4483 ics_getting_history == H_GOT_UNREQ_HEADER) {
4484 /* This was an initial position from a move list, not
4485 the current position */
4489 /* Update currentMove and known move number limits */
4490 newMove = newGame || moveNum > forwardMostMove;
4493 forwardMostMove = backwardMostMove = currentMove = moveNum;
4494 if (gameMode == IcsExamining && moveNum == 0) {
4495 /* Workaround for ICS limitation: we are not told the wild
4496 type when starting to examine a game. But if we ask for
4497 the move list, the move list header will tell us */
4498 ics_getting_history = H_REQUESTED;
4499 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4502 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4503 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4505 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4506 /* [HGM] applied this also to an engine that is silently watching */
4507 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4508 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4509 gameInfo.variant == currentlyInitializedVariant) {
4510 takeback = forwardMostMove - moveNum;
4511 for (i = 0; i < takeback; i++) {
4512 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4513 SendToProgram("undo\n", &first);
4518 forwardMostMove = moveNum;
4519 if (!pausing || currentMove > forwardMostMove)
4520 currentMove = forwardMostMove;
4522 /* New part of history that is not contiguous with old part */
4523 if (pausing && gameMode == IcsExamining) {
4524 pauseExamInvalid = TRUE;
4525 forwardMostMove = pauseExamForwardMostMove;
4528 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4530 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4531 // [HGM] when we will receive the move list we now request, it will be
4532 // fed to the engine from the first move on. So if the engine is not
4533 // in the initial position now, bring it there.
4534 InitChessProgram(&first, 0);
4537 ics_getting_history = H_REQUESTED;
4538 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541 forwardMostMove = backwardMostMove = currentMove = moveNum;
4544 /* Update the clocks */
4545 if (strchr(elapsed_time, '.')) {
4547 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4548 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4550 /* Time is in seconds */
4551 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4552 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4557 if (appData.zippyPlay && newGame &&
4558 gameMode != IcsObserving && gameMode != IcsIdle &&
4559 gameMode != IcsExamining)
4560 ZippyFirstBoard(moveNum, basetime, increment);
4563 /* Put the move on the move list, first converting
4564 to canonical algebraic form. */
4566 if (appData.debugMode) {
4567 if (appData.debugMode) { int f = forwardMostMove;
4568 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4569 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4570 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4572 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4573 fprintf(debugFP, "moveNum = %d\n", moveNum);
4574 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4575 setbuf(debugFP, NULL);
4577 if (moveNum <= backwardMostMove) {
4578 /* We don't know what the board looked like before
4580 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4581 strcat(parseList[moveNum - 1], " ");
4582 strcat(parseList[moveNum - 1], elapsed_time);
4583 moveList[moveNum - 1][0] = NULLCHAR;
4584 } else if (strcmp(move_str, "none") == 0) {
4585 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4586 /* Again, we don't know what the board looked like;
4587 this is really the start of the game. */
4588 parseList[moveNum - 1][0] = NULLCHAR;
4589 moveList[moveNum - 1][0] = NULLCHAR;
4590 backwardMostMove = moveNum;
4591 startedFromSetupPosition = TRUE;
4592 fromX = fromY = toX = toY = -1;
4594 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4595 // So we parse the long-algebraic move string in stead of the SAN move
4596 int valid; char buf[MSG_SIZ], *prom;
4598 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4599 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4600 // str looks something like "Q/a1-a2"; kill the slash
4602 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4603 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4604 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4605 strcat(buf, prom); // long move lacks promo specification!
4606 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4607 if(appData.debugMode)
4608 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4609 safeStrCpy(move_str, buf, MSG_SIZ);
4611 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4612 &fromX, &fromY, &toX, &toY, &promoChar)
4613 || ParseOneMove(buf, moveNum - 1, &moveType,
4614 &fromX, &fromY, &toX, &toY, &promoChar);
4615 // end of long SAN patch
4617 (void) CoordsToAlgebraic(boards[moveNum - 1],
4618 PosFlags(moveNum - 1),
4619 fromY, fromX, toY, toX, promoChar,
4620 parseList[moveNum-1]);
4621 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4627 if(gameInfo.variant != VariantShogi)
4628 strcat(parseList[moveNum - 1], "+");
4631 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4632 strcat(parseList[moveNum - 1], "#");
4635 strcat(parseList[moveNum - 1], " ");
4636 strcat(parseList[moveNum - 1], elapsed_time);
4637 /* currentMoveString is set as a side-effect of ParseOneMove */
4638 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4639 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4640 strcat(moveList[moveNum - 1], "\n");
4642 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4643 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4644 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4645 ChessSquare old, new = boards[moveNum][k][j];
4646 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4647 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4648 if(old == new) continue;
4649 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4650 else if(new == WhiteWazir || new == BlackWazir) {
4651 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4652 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4653 else boards[moveNum][k][j] = old; // preserve type of Gold
4654 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4655 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4658 /* Move from ICS was illegal!? Punt. */
4659 if (appData.debugMode) {
4660 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4661 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4663 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4664 strcat(parseList[moveNum - 1], " ");
4665 strcat(parseList[moveNum - 1], elapsed_time);
4666 moveList[moveNum - 1][0] = NULLCHAR;
4667 fromX = fromY = toX = toY = -1;
4670 if (appData.debugMode) {
4671 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4672 setbuf(debugFP, NULL);
4676 /* Send move to chess program (BEFORE animating it). */
4677 if (appData.zippyPlay && !newGame && newMove &&
4678 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4680 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4681 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4682 if (moveList[moveNum - 1][0] == NULLCHAR) {
4683 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4685 DisplayError(str, 0);
4687 if (first.sendTime) {
4688 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4690 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4691 if (firstMove && !bookHit) {
4693 if (first.useColors) {
4694 SendToProgram(gameMode == IcsPlayingWhite ?
4696 "black\ngo\n", &first);
4698 SendToProgram("go\n", &first);
4700 first.maybeThinking = TRUE;
4703 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4704 if (moveList[moveNum - 1][0] == NULLCHAR) {
4705 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4706 DisplayError(str, 0);
4708 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4709 SendMoveToProgram(moveNum - 1, &first);
4716 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4717 /* If move comes from a remote source, animate it. If it
4718 isn't remote, it will have already been animated. */
4719 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4720 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4722 if (!pausing && appData.highlightLastMove) {
4723 SetHighlights(fromX, fromY, toX, toY);
4727 /* Start the clocks */
4728 whiteFlag = blackFlag = FALSE;
4729 appData.clockMode = !(basetime == 0 && increment == 0);
4731 ics_clock_paused = TRUE;
4733 } else if (ticking == 1) {
4734 ics_clock_paused = FALSE;
4736 if (gameMode == IcsIdle ||
4737 relation == RELATION_OBSERVING_STATIC ||
4738 relation == RELATION_EXAMINING ||
4740 DisplayBothClocks();
4744 /* Display opponents and material strengths */
4745 if (gameInfo.variant != VariantBughouse &&
4746 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4747 if (tinyLayout || smallLayout) {
4748 if(gameInfo.variant == VariantNormal)
4749 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4750 gameInfo.white, white_stren, gameInfo.black, black_stren,
4751 basetime, increment);
4753 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4754 gameInfo.white, white_stren, gameInfo.black, black_stren,
4755 basetime, increment, (int) gameInfo.variant);
4757 if(gameInfo.variant == VariantNormal)
4758 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4759 gameInfo.white, white_stren, gameInfo.black, black_stren,
4760 basetime, increment);
4762 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4763 gameInfo.white, white_stren, gameInfo.black, black_stren,
4764 basetime, increment, VariantName(gameInfo.variant));
4767 if (appData.debugMode) {
4768 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4773 /* Display the board */
4774 if (!pausing && !appData.noGUI) {
4776 if (appData.premove)
4778 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4779 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4780 ClearPremoveHighlights();
4782 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4783 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4784 DrawPosition(j, boards[currentMove]);
4786 DisplayMove(moveNum - 1);
4787 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4788 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4789 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4790 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4794 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4796 if(bookHit) { // [HGM] book: simulate book reply
4797 static char bookMove[MSG_SIZ]; // a bit generous?
4799 programStats.nodes = programStats.depth = programStats.time =
4800 programStats.score = programStats.got_only_move = 0;
4801 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4803 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4804 strcat(bookMove, bookHit);
4805 HandleMachineMove(bookMove, &first);
4814 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4815 ics_getting_history = H_REQUESTED;
4816 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4822 AnalysisPeriodicEvent(force)
4825 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4826 && !force) || !appData.periodicUpdates)
4829 /* Send . command to Crafty to collect stats */
4830 SendToProgram(".\n", &first);
4832 /* Don't send another until we get a response (this makes
4833 us stop sending to old Crafty's which don't understand
4834 the "." command (sending illegal cmds resets node count & time,
4835 which looks bad)) */
4836 programStats.ok_to_send = 0;
4839 void ics_update_width(new_width)
4842 ics_printf("set width %d\n", new_width);
4846 SendMoveToProgram(moveNum, cps)
4848 ChessProgramState *cps;
4852 if (cps->useUsermove) {
4853 SendToProgram("usermove ", cps);
4857 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4858 int len = space - parseList[moveNum];
4859 memcpy(buf, parseList[moveNum], len);
4861 buf[len] = NULLCHAR;
4863 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4865 SendToProgram(buf, cps);
4867 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4868 AlphaRank(moveList[moveNum], 4);
4869 SendToProgram(moveList[moveNum], cps);
4870 AlphaRank(moveList[moveNum], 4); // and back
4872 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4873 * the engine. It would be nice to have a better way to identify castle
4875 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4876 && cps->useOOCastle) {
4877 int fromX = moveList[moveNum][0] - AAA;
4878 int fromY = moveList[moveNum][1] - ONE;
4879 int toX = moveList[moveNum][2] - AAA;
4880 int toY = moveList[moveNum][3] - ONE;
4881 if((boards[moveNum][fromY][fromX] == WhiteKing
4882 && boards[moveNum][toY][toX] == WhiteRook)
4883 || (boards[moveNum][fromY][fromX] == BlackKing
4884 && boards[moveNum][toY][toX] == BlackRook)) {
4885 if(toX > fromX) SendToProgram("O-O\n", cps);
4886 else SendToProgram("O-O-O\n", cps);
4888 else SendToProgram(moveList[moveNum], cps);
4890 else SendToProgram(moveList[moveNum], cps);
4891 /* End of additions by Tord */
4894 /* [HGM] setting up the opening has brought engine in force mode! */
4895 /* Send 'go' if we are in a mode where machine should play. */
4896 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4897 (gameMode == TwoMachinesPlay ||
4899 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4901 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4902 SendToProgram("go\n", cps);
4903 if (appData.debugMode) {
4904 fprintf(debugFP, "(extra)\n");
4907 setboardSpoiledMachineBlack = 0;
4911 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4913 int fromX, fromY, toX, toY;
4916 char user_move[MSG_SIZ];
4920 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4921 (int)moveType, fromX, fromY, toX, toY);
4922 DisplayError(user_move + strlen("say "), 0);
4924 case WhiteKingSideCastle:
4925 case BlackKingSideCastle:
4926 case WhiteQueenSideCastleWild:
4927 case BlackQueenSideCastleWild:
4929 case WhiteHSideCastleFR:
4930 case BlackHSideCastleFR:
4932 snprintf(user_move, MSG_SIZ, "o-o\n");
4934 case WhiteQueenSideCastle:
4935 case BlackQueenSideCastle:
4936 case WhiteKingSideCastleWild:
4937 case BlackKingSideCastleWild:
4939 case WhiteASideCastleFR:
4940 case BlackASideCastleFR:
4942 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4944 case WhiteNonPromotion:
4945 case BlackNonPromotion:
4946 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4948 case WhitePromotion:
4949 case BlackPromotion:
4950 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4951 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4952 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4953 PieceToChar(WhiteFerz));
4954 else if(gameInfo.variant == VariantGreat)
4955 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4956 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4957 PieceToChar(WhiteMan));
4959 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4960 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4966 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4967 ToUpper(PieceToChar((ChessSquare) fromX)),
4968 AAA + toX, ONE + toY);
4970 case IllegalMove: /* could be a variant we don't quite understand */
4971 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4973 case WhiteCapturesEnPassant:
4974 case BlackCapturesEnPassant:
4975 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4976 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4979 SendToICS(user_move);
4980 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4981 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4986 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4987 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4988 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4989 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4990 DisplayError("You cannot do this while you are playing or observing", 0);
4993 if(gameMode != IcsExamining) { // is this ever not the case?
4994 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4996 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4997 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4998 } else { // on FICS we must first go to general examine mode
4999 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5001 if(gameInfo.variant != VariantNormal) {
5002 // try figure out wild number, as xboard names are not always valid on ICS
5003 for(i=1; i<=36; i++) {
5004 snprintf(buf, MSG_SIZ, "wild/%d", i);
5005 if(StringToVariant(buf) == gameInfo.variant) break;
5007 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5008 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5009 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5010 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5011 SendToICS(ics_prefix);
5013 if(startedFromSetupPosition || backwardMostMove != 0) {
5014 fen = PositionToFEN(backwardMostMove, NULL);
5015 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5016 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5018 } else { // FICS: everything has to set by separate bsetup commands
5019 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5020 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5022 if(!WhiteOnMove(backwardMostMove)) {
5023 SendToICS("bsetup tomove black\n");
5025 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5026 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5028 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5029 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5031 i = boards[backwardMostMove][EP_STATUS];
5032 if(i >= 0) { // set e.p.
5033 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5039 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5040 SendToICS("bsetup done\n"); // switch to normal examining.
5042 for(i = backwardMostMove; i<last; i++) {
5044 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5047 SendToICS(ics_prefix);
5048 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5052 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5057 if (rf == DROP_RANK) {
5058 sprintf(move, "%c@%c%c\n",
5059 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5061 if (promoChar == 'x' || promoChar == NULLCHAR) {
5062 sprintf(move, "%c%c%c%c\n",
5063 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5065 sprintf(move, "%c%c%c%c%c\n",
5066 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5072 ProcessICSInitScript(f)
5077 while (fgets(buf, MSG_SIZ, f)) {
5078 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5085 static int lastX, lastY, selectFlag, dragging;
5090 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5091 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5092 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5093 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5094 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5095 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5098 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5099 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5100 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5101 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5103 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5104 appData.testLegality && (promoSweep == king ||
5105 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5106 ChangeDragPiece(promoSweep);
5109 int PromoScroll(int x, int y)
5113 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5114 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5115 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5116 if(!step) return FALSE;
5117 lastX = x; lastY = y;
5118 if((promoSweep < BlackPawn) == flipView) step = -step;
5119 if(step > 0) selectFlag = 1;
5120 if(!selectFlag) Sweep(step);
5127 ChessSquare piece = boards[currentMove][toY][toX];
5130 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5131 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5132 if(!step) step = -1;
5133 } while(PieceToChar(pieceSweep) == '.');
5134 boards[currentMove][toY][toX] = pieceSweep;
5135 DrawPosition(FALSE, boards[currentMove]);
5136 boards[currentMove][toY][toX] = piece;
5138 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5140 AlphaRank(char *move, int n)
5142 // char *p = move, c; int x, y;
5144 if (appData.debugMode) {
5145 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5149 move[2]>='0' && move[2]<='9' &&
5150 move[3]>='a' && move[3]<='x' ) {
5152 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5153 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5155 if(move[0]>='0' && move[0]<='9' &&
5156 move[1]>='a' && move[1]<='x' &&
5157 move[2]>='0' && move[2]<='9' &&
5158 move[3]>='a' && move[3]<='x' ) {
5159 /* input move, Shogi -> normal */
5160 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5161 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5162 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5163 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5166 move[3]>='0' && move[3]<='9' &&
5167 move[2]>='a' && move[2]<='x' ) {
5169 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5170 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5173 move[0]>='a' && move[0]<='x' &&
5174 move[3]>='0' && move[3]<='9' &&
5175 move[2]>='a' && move[2]<='x' ) {
5176 /* output move, normal -> Shogi */
5177 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5178 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5179 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5180 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5181 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5183 if (appData.debugMode) {
5184 fprintf(debugFP, " out = '%s'\n", move);
5188 char yy_textstr[8000];
5190 /* Parser for moves from gnuchess, ICS, or user typein box */
5192 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5195 ChessMove *moveType;
5196 int *fromX, *fromY, *toX, *toY;
5199 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5201 switch (*moveType) {
5202 case WhitePromotion:
5203 case BlackPromotion:
5204 case WhiteNonPromotion:
5205 case BlackNonPromotion:
5207 case WhiteCapturesEnPassant:
5208 case BlackCapturesEnPassant:
5209 case WhiteKingSideCastle:
5210 case WhiteQueenSideCastle:
5211 case BlackKingSideCastle:
5212 case BlackQueenSideCastle:
5213 case WhiteKingSideCastleWild:
5214 case WhiteQueenSideCastleWild:
5215 case BlackKingSideCastleWild:
5216 case BlackQueenSideCastleWild:
5217 /* Code added by Tord: */
5218 case WhiteHSideCastleFR:
5219 case WhiteASideCastleFR:
5220 case BlackHSideCastleFR:
5221 case BlackASideCastleFR:
5222 /* End of code added by Tord */
5223 case IllegalMove: /* bug or odd chess variant */
5224 *fromX = currentMoveString[0] - AAA;
5225 *fromY = currentMoveString[1] - ONE;
5226 *toX = currentMoveString[2] - AAA;
5227 *toY = currentMoveString[3] - ONE;
5228 *promoChar = currentMoveString[4];
5229 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5230 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5231 if (appData.debugMode) {
5232 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5234 *fromX = *fromY = *toX = *toY = 0;
5237 if (appData.testLegality) {
5238 return (*moveType != IllegalMove);
5240 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5241 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5246 *fromX = *moveType == WhiteDrop ?
5247 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5248 (int) CharToPiece(ToLower(currentMoveString[0]));
5250 *toX = currentMoveString[2] - AAA;
5251 *toY = currentMoveString[3] - ONE;
5252 *promoChar = NULLCHAR;
5256 case ImpossibleMove:
5266 if (appData.debugMode) {
5267 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5270 *fromX = *fromY = *toX = *toY = 0;
5271 *promoChar = NULLCHAR;
5276 Boolean pushed = FALSE;
5279 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5280 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5281 int fromX, fromY, toX, toY; char promoChar;
5286 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5287 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5290 endPV = forwardMostMove;
5292 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5293 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5294 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5295 if(appData.debugMode){
5296 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);
5298 if(!valid && nr == 0 &&
5299 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5300 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5301 // Hande case where played move is different from leading PV move
5302 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5303 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5304 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5305 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5306 endPV += 2; // if position different, keep this
5307 moveList[endPV-1][0] = fromX + AAA;
5308 moveList[endPV-1][1] = fromY + ONE;
5309 moveList[endPV-1][2] = toX + AAA;
5310 moveList[endPV-1][3] = toY + ONE;
5311 parseList[endPV-1][0] = NULLCHAR;
5312 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5315 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5316 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5317 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5318 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5319 valid++; // allow comments in PV
5323 if(endPV+1 > framePtr) break; // no space, truncate
5326 CopyBoard(boards[endPV], boards[endPV-1]);
5327 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5328 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5329 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5330 CoordsToAlgebraic(boards[endPV - 1],
5331 PosFlags(endPV - 1),
5332 fromY, fromX, toY, toX, promoChar,
5333 parseList[endPV - 1]);
5335 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5336 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5337 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5338 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5339 DrawPosition(TRUE, boards[currentMove]);
5343 MultiPV(ChessProgramState *cps)
5344 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5346 for(i=0; i<cps->nrOptions; i++)
5347 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5353 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5355 int startPV, multi, lineStart, origIndex = index;
5356 char *p, buf2[MSG_SIZ];
5358 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5359 lastX = x; lastY = y;
5360 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5361 lineStart = startPV = index;
5362 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5363 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5365 do{ while(buf[index] && buf[index] != '\n') index++;
5366 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5368 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5369 int n = first.option[multi].value;
5370 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5371 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5372 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5373 first.option[multi].value = n;
5377 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5378 *start = startPV; *end = index-1;
5383 LoadPV(int x, int y)
5384 { // called on right mouse click to load PV
5385 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5386 lastX = x; lastY = y;
5387 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5394 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5395 if(endPV < 0) return;
5397 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5398 Boolean saveAnimate = appData.animate;
5400 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5401 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5402 } else storedGames--; // abandon shelved tail of original game
5405 forwardMostMove = currentMove;
5406 currentMove = oldFMM;
5407 appData.animate = FALSE;
5408 ToNrEvent(forwardMostMove);
5409 appData.animate = saveAnimate;
5411 currentMove = forwardMostMove;
5412 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5413 ClearPremoveHighlights();
5414 DrawPosition(TRUE, boards[currentMove]);
5418 MovePV(int x, int y, int h)
5419 { // step through PV based on mouse coordinates (called on mouse move)
5420 int margin = h>>3, step = 0;
5422 // we must somehow check if right button is still down (might be released off board!)
5423 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5424 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5425 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5427 lastX = x; lastY = y;
5429 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5430 if(endPV < 0) return;
5431 if(y < margin) step = 1; else
5432 if(y > h - margin) step = -1;
5433 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5434 currentMove += step;
5435 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5436 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5437 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5438 DrawPosition(FALSE, boards[currentMove]);
5442 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5443 // All positions will have equal probability, but the current method will not provide a unique
5444 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5450 int piecesLeft[(int)BlackPawn];
5451 int seed, nrOfShuffles;
5453 void GetPositionNumber()
5454 { // sets global variable seed
5457 seed = appData.defaultFrcPosition;
5458 if(seed < 0) { // randomize based on time for negative FRC position numbers
5459 for(i=0; i<50; i++) seed += random();
5460 seed = random() ^ random() >> 8 ^ random() << 8;
5461 if(seed<0) seed = -seed;
5465 int put(Board board, int pieceType, int rank, int n, int shade)
5466 // put the piece on the (n-1)-th empty squares of the given shade
5470 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5471 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5472 board[rank][i] = (ChessSquare) pieceType;
5473 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5475 piecesLeft[pieceType]--;
5483 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5484 // calculate where the next piece goes, (any empty square), and put it there
5488 i = seed % squaresLeft[shade];
5489 nrOfShuffles *= squaresLeft[shade];
5490 seed /= squaresLeft[shade];
5491 put(board, pieceType, rank, i, shade);
5494 void AddTwoPieces(Board board, int pieceType, int rank)
5495 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5497 int i, n=squaresLeft[ANY], j=n-1, k;
5499 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5500 i = seed % k; // pick one
5503 while(i >= j) i -= j--;
5504 j = n - 1 - j; i += j;
5505 put(board, pieceType, rank, j, ANY);
5506 put(board, pieceType, rank, i, ANY);
5509 void SetUpShuffle(Board board, int number)
5513 GetPositionNumber(); nrOfShuffles = 1;
5515 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5516 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5517 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5519 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5521 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5522 p = (int) board[0][i];
5523 if(p < (int) BlackPawn) piecesLeft[p] ++;
5524 board[0][i] = EmptySquare;
5527 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5528 // shuffles restricted to allow normal castling put KRR first
5529 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5530 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5531 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5532 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5533 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5534 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5535 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5536 put(board, WhiteRook, 0, 0, ANY);
5537 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5540 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5541 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5542 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5543 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5544 while(piecesLeft[p] >= 2) {
5545 AddOnePiece(board, p, 0, LITE);
5546 AddOnePiece(board, p, 0, DARK);
5548 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5551 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5552 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5553 // but we leave King and Rooks for last, to possibly obey FRC restriction
5554 if(p == (int)WhiteRook) continue;
5555 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5556 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5559 // now everything is placed, except perhaps King (Unicorn) and Rooks
5561 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5562 // Last King gets castling rights
5563 while(piecesLeft[(int)WhiteUnicorn]) {
5564 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5568 while(piecesLeft[(int)WhiteKing]) {
5569 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5570 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5575 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5576 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5579 // Only Rooks can be left; simply place them all
5580 while(piecesLeft[(int)WhiteRook]) {
5581 i = put(board, WhiteRook, 0, 0, ANY);
5582 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5585 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5587 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5590 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5591 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5594 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5597 int SetCharTable( char *table, const char * map )
5598 /* [HGM] moved here from winboard.c because of its general usefulness */
5599 /* Basically a safe strcpy that uses the last character as King */
5601 int result = FALSE; int NrPieces;
5603 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5604 && NrPieces >= 12 && !(NrPieces&1)) {
5605 int i; /* [HGM] Accept even length from 12 to 34 */
5607 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5608 for( i=0; i<NrPieces/2-1; i++ ) {
5610 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5612 table[(int) WhiteKing] = map[NrPieces/2-1];
5613 table[(int) BlackKing] = map[NrPieces-1];
5621 void Prelude(Board board)
5622 { // [HGM] superchess: random selection of exo-pieces
5623 int i, j, k; ChessSquare p;
5624 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5626 GetPositionNumber(); // use FRC position number
5628 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5629 SetCharTable(pieceToChar, appData.pieceToCharTable);
5630 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5631 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5634 j = seed%4; seed /= 4;
5635 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5636 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5637 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5638 j = seed%3 + (seed%3 >= j); seed /= 3;
5639 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5640 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5641 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5642 j = seed%3; seed /= 3;
5643 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5644 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5645 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5646 j = seed%2 + (seed%2 >= j); seed /= 2;
5647 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5648 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5649 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5650 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5651 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5652 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5653 put(board, exoPieces[0], 0, 0, ANY);
5654 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5658 InitPosition(redraw)
5661 ChessSquare (* pieces)[BOARD_FILES];
5662 int i, j, pawnRow, overrule,
5663 oldx = gameInfo.boardWidth,
5664 oldy = gameInfo.boardHeight,
5665 oldh = gameInfo.holdingsWidth;
5668 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5670 /* [AS] Initialize pv info list [HGM] and game status */
5672 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5673 pvInfoList[i].depth = 0;
5674 boards[i][EP_STATUS] = EP_NONE;
5675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5678 initialRulePlies = 0; /* 50-move counter start */
5680 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5681 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5685 /* [HGM] logic here is completely changed. In stead of full positions */
5686 /* the initialized data only consist of the two backranks. The switch */
5687 /* selects which one we will use, which is than copied to the Board */
5688 /* initialPosition, which for the rest is initialized by Pawns and */
5689 /* empty squares. This initial position is then copied to boards[0], */
5690 /* possibly after shuffling, so that it remains available. */
5692 gameInfo.holdingsWidth = 0; /* default board sizes */
5693 gameInfo.boardWidth = 8;
5694 gameInfo.boardHeight = 8;
5695 gameInfo.holdingsSize = 0;
5696 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5697 for(i=0; i<BOARD_FILES-2; i++)
5698 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5699 initialPosition[EP_STATUS] = EP_NONE;
5700 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5701 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5702 SetCharTable(pieceNickName, appData.pieceNickNames);
5703 else SetCharTable(pieceNickName, "............");
5706 switch (gameInfo.variant) {
5707 case VariantFischeRandom:
5708 shuffleOpenings = TRUE;
5711 case VariantShatranj:
5712 pieces = ShatranjArray;
5713 nrCastlingRights = 0;
5714 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5717 pieces = makrukArray;
5718 nrCastlingRights = 0;
5719 startedFromSetupPosition = TRUE;
5720 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5722 case VariantTwoKings:
5723 pieces = twoKingsArray;
5725 case VariantCapaRandom:
5726 shuffleOpenings = TRUE;
5727 case VariantCapablanca:
5728 pieces = CapablancaArray;
5729 gameInfo.boardWidth = 10;
5730 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5733 pieces = GothicArray;
5734 gameInfo.boardWidth = 10;
5735 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5738 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5739 gameInfo.holdingsSize = 7;
5742 pieces = JanusArray;
5743 gameInfo.boardWidth = 10;
5744 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5745 nrCastlingRights = 6;
5746 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5747 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5748 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5749 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5750 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5751 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5754 pieces = FalconArray;
5755 gameInfo.boardWidth = 10;
5756 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5758 case VariantXiangqi:
5759 pieces = XiangqiArray;
5760 gameInfo.boardWidth = 9;
5761 gameInfo.boardHeight = 10;
5762 nrCastlingRights = 0;
5763 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5766 pieces = ShogiArray;
5767 gameInfo.boardWidth = 9;
5768 gameInfo.boardHeight = 9;
5769 gameInfo.holdingsSize = 7;
5770 nrCastlingRights = 0;
5771 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5773 case VariantCourier:
5774 pieces = CourierArray;
5775 gameInfo.boardWidth = 12;
5776 nrCastlingRights = 0;
5777 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5779 case VariantKnightmate:
5780 pieces = KnightmateArray;
5781 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5783 case VariantSpartan:
5784 pieces = SpartanArray;
5785 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5788 pieces = fairyArray;
5789 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5792 pieces = GreatArray;
5793 gameInfo.boardWidth = 10;
5794 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5795 gameInfo.holdingsSize = 8;
5799 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5800 gameInfo.holdingsSize = 8;
5801 startedFromSetupPosition = TRUE;
5803 case VariantCrazyhouse:
5804 case VariantBughouse:
5806 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5807 gameInfo.holdingsSize = 5;
5809 case VariantWildCastle:
5811 /* !!?shuffle with kings guaranteed to be on d or e file */
5812 shuffleOpenings = 1;
5814 case VariantNoCastle:
5816 nrCastlingRights = 0;
5817 /* !!?unconstrained back-rank shuffle */
5818 shuffleOpenings = 1;
5823 if(appData.NrFiles >= 0) {
5824 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5825 gameInfo.boardWidth = appData.NrFiles;
5827 if(appData.NrRanks >= 0) {
5828 gameInfo.boardHeight = appData.NrRanks;
5830 if(appData.holdingsSize >= 0) {
5831 i = appData.holdingsSize;
5832 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5833 gameInfo.holdingsSize = i;
5835 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5836 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5837 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5839 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5840 if(pawnRow < 1) pawnRow = 1;
5841 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5843 /* User pieceToChar list overrules defaults */
5844 if(appData.pieceToCharTable != NULL)
5845 SetCharTable(pieceToChar, appData.pieceToCharTable);
5847 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5849 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5850 s = (ChessSquare) 0; /* account holding counts in guard band */
5851 for( i=0; i<BOARD_HEIGHT; i++ )
5852 initialPosition[i][j] = s;
5854 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5855 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5856 initialPosition[pawnRow][j] = WhitePawn;
5857 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5858 if(gameInfo.variant == VariantXiangqi) {
5860 initialPosition[pawnRow][j] =
5861 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5862 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5863 initialPosition[2][j] = WhiteCannon;
5864 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5868 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5870 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5873 initialPosition[1][j] = WhiteBishop;
5874 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5876 initialPosition[1][j] = WhiteRook;
5877 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5880 if( nrCastlingRights == -1) {
5881 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5882 /* This sets default castling rights from none to normal corners */
5883 /* Variants with other castling rights must set them themselves above */
5884 nrCastlingRights = 6;
5886 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5887 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5888 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5889 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5890 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5891 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5894 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5895 if(gameInfo.variant == VariantGreat) { // promotion commoners
5896 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5897 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5898 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5899 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5901 if( gameInfo.variant == VariantSChess ) {
5902 initialPosition[1][0] = BlackMarshall;
5903 initialPosition[2][0] = BlackAngel;
5904 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5905 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5906 initialPosition[1][1] = initialPosition[2][1] =
5907 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5909 if (appData.debugMode) {
5910 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5912 if(shuffleOpenings) {
5913 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5914 startedFromSetupPosition = TRUE;
5916 if(startedFromPositionFile) {
5917 /* [HGM] loadPos: use PositionFile for every new game */
5918 CopyBoard(initialPosition, filePosition);
5919 for(i=0; i<nrCastlingRights; i++)
5920 initialRights[i] = filePosition[CASTLING][i];
5921 startedFromSetupPosition = TRUE;
5924 CopyBoard(boards[0], initialPosition);
5926 if(oldx != gameInfo.boardWidth ||
5927 oldy != gameInfo.boardHeight ||
5928 oldv != gameInfo.variant ||
5929 oldh != gameInfo.holdingsWidth
5931 InitDrawingSizes(-2 ,0);
5933 oldv = gameInfo.variant;
5935 DrawPosition(TRUE, boards[currentMove]);
5939 SendBoard(cps, moveNum)
5940 ChessProgramState *cps;
5943 char message[MSG_SIZ];
5945 if (cps->useSetboard) {
5946 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5947 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5948 SendToProgram(message, cps);
5954 /* Kludge to set black to move, avoiding the troublesome and now
5955 * deprecated "black" command.
5957 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5958 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5960 SendToProgram("edit\n", cps);
5961 SendToProgram("#\n", cps);
5962 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5963 bp = &boards[moveNum][i][BOARD_LEFT];
5964 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5965 if ((int) *bp < (int) BlackPawn) {
5966 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5968 if(message[0] == '+' || message[0] == '~') {
5969 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5970 PieceToChar((ChessSquare)(DEMOTED *bp)),
5973 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5974 message[1] = BOARD_RGHT - 1 - j + '1';
5975 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5977 SendToProgram(message, cps);
5982 SendToProgram("c\n", cps);
5983 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5984 bp = &boards[moveNum][i][BOARD_LEFT];
5985 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5986 if (((int) *bp != (int) EmptySquare)
5987 && ((int) *bp >= (int) BlackPawn)) {
5988 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5990 if(message[0] == '+' || message[0] == '~') {
5991 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5992 PieceToChar((ChessSquare)(DEMOTED *bp)),
5995 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5996 message[1] = BOARD_RGHT - 1 - j + '1';
5997 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5999 SendToProgram(message, cps);
6004 SendToProgram(".\n", cps);
6006 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6010 DefaultPromoChoice(int white)
6013 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6014 result = WhiteFerz; // no choice
6015 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6016 result= WhiteKing; // in Suicide Q is the last thing we want
6017 else if(gameInfo.variant == VariantSpartan)
6018 result = white ? WhiteQueen : WhiteAngel;
6019 else result = WhiteQueen;
6020 if(!white) result = WHITE_TO_BLACK result;
6024 static int autoQueen; // [HGM] oneclick
6027 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6029 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6030 /* [HGM] add Shogi promotions */
6031 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6036 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6037 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6039 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6040 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6043 piece = boards[currentMove][fromY][fromX];
6044 if(gameInfo.variant == VariantShogi) {
6045 promotionZoneSize = BOARD_HEIGHT/3;
6046 highestPromotingPiece = (int)WhiteFerz;
6047 } else if(gameInfo.variant == VariantMakruk) {
6048 promotionZoneSize = 3;
6051 // Treat Lance as Pawn when it is not representing Amazon
6052 if(gameInfo.variant != VariantSuper) {
6053 if(piece == WhiteLance) piece = WhitePawn; else
6054 if(piece == BlackLance) piece = BlackPawn;
6057 // next weed out all moves that do not touch the promotion zone at all
6058 if((int)piece >= BlackPawn) {
6059 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6061 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6063 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6064 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6067 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6069 // weed out mandatory Shogi promotions
6070 if(gameInfo.variant == VariantShogi) {
6071 if(piece >= BlackPawn) {
6072 if(toY == 0 && piece == BlackPawn ||
6073 toY == 0 && piece == BlackQueen ||
6074 toY <= 1 && piece == BlackKnight) {
6079 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6080 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6081 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6088 // weed out obviously illegal Pawn moves
6089 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6090 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6091 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6092 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6093 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6094 // note we are not allowed to test for valid (non-)capture, due to premove
6097 // we either have a choice what to promote to, or (in Shogi) whether to promote
6098 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6099 *promoChoice = PieceToChar(BlackFerz); // no choice
6102 // no sense asking what we must promote to if it is going to explode...
6103 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6104 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6107 // give caller the default choice even if we will not make it
6108 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6109 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6110 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6111 && gameInfo.variant != VariantShogi
6112 && gameInfo.variant != VariantSuper) return FALSE;
6113 if(autoQueen) return FALSE; // predetermined
6115 // suppress promotion popup on illegal moves that are not premoves
6116 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6117 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6118 if(appData.testLegality && !premove) {
6119 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6120 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6121 if(moveType != WhitePromotion && moveType != BlackPromotion)
6129 InPalace(row, column)
6131 { /* [HGM] for Xiangqi */
6132 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6133 column < (BOARD_WIDTH + 4)/2 &&
6134 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6139 PieceForSquare (x, y)
6143 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6146 return boards[currentMove][y][x];
6150 OKToStartUserMove(x, y)
6153 ChessSquare from_piece;
6156 if (matchMode) return FALSE;
6157 if (gameMode == EditPosition) return TRUE;
6159 if (x >= 0 && y >= 0)
6160 from_piece = boards[currentMove][y][x];
6162 from_piece = EmptySquare;
6164 if (from_piece == EmptySquare) return FALSE;
6166 white_piece = (int)from_piece >= (int)WhitePawn &&
6167 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6170 case PlayFromGameFile:
6172 case TwoMachinesPlay:
6180 case MachinePlaysWhite:
6181 case IcsPlayingBlack:
6182 if (appData.zippyPlay) return FALSE;
6184 DisplayMoveError(_("You are playing Black"));
6189 case MachinePlaysBlack:
6190 case IcsPlayingWhite:
6191 if (appData.zippyPlay) return FALSE;
6193 DisplayMoveError(_("You are playing White"));
6199 if (!white_piece && WhiteOnMove(currentMove)) {
6200 DisplayMoveError(_("It is White's turn"));
6203 if (white_piece && !WhiteOnMove(currentMove)) {
6204 DisplayMoveError(_("It is Black's turn"));
6207 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6208 /* Editing correspondence game history */
6209 /* Could disallow this or prompt for confirmation */
6214 case BeginningOfGame:
6215 if (appData.icsActive) return FALSE;
6216 if (!appData.noChessProgram) {
6218 DisplayMoveError(_("You are playing White"));
6225 if (!white_piece && WhiteOnMove(currentMove)) {
6226 DisplayMoveError(_("It is White's turn"));
6229 if (white_piece && !WhiteOnMove(currentMove)) {
6230 DisplayMoveError(_("It is Black's turn"));
6239 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6240 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6241 && gameMode != AnalyzeFile && gameMode != Training) {
6242 DisplayMoveError(_("Displayed position is not current"));
6249 OnlyMove(int *x, int *y, Boolean captures) {
6250 DisambiguateClosure cl;
6251 if (appData.zippyPlay) return FALSE;
6253 case MachinePlaysBlack:
6254 case IcsPlayingWhite:
6255 case BeginningOfGame:
6256 if(!WhiteOnMove(currentMove)) return FALSE;
6258 case MachinePlaysWhite:
6259 case IcsPlayingBlack:
6260 if(WhiteOnMove(currentMove)) return FALSE;
6267 cl.pieceIn = EmptySquare;
6272 cl.promoCharIn = NULLCHAR;
6273 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6274 if( cl.kind == NormalMove ||
6275 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6276 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6277 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6284 if(cl.kind != ImpossibleMove) return FALSE;
6285 cl.pieceIn = EmptySquare;
6290 cl.promoCharIn = NULLCHAR;
6291 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6292 if( cl.kind == NormalMove ||
6293 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6294 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6295 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6300 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6306 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6307 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6308 int lastLoadGameUseList = FALSE;
6309 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6310 ChessMove lastLoadGameStart = EndOfFile;
6313 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6314 int fromX, fromY, toX, toY;
6318 ChessSquare pdown, pup;
6320 /* Check if the user is playing in turn. This is complicated because we
6321 let the user "pick up" a piece before it is his turn. So the piece he
6322 tried to pick up may have been captured by the time he puts it down!
6323 Therefore we use the color the user is supposed to be playing in this
6324 test, not the color of the piece that is currently on the starting
6325 square---except in EditGame mode, where the user is playing both
6326 sides; fortunately there the capture race can't happen. (It can
6327 now happen in IcsExamining mode, but that's just too bad. The user
6328 will get a somewhat confusing message in that case.)
6332 case PlayFromGameFile:
6334 case TwoMachinesPlay:
6338 /* We switched into a game mode where moves are not accepted,
6339 perhaps while the mouse button was down. */
6342 case MachinePlaysWhite:
6343 /* User is moving for Black */
6344 if (WhiteOnMove(currentMove)) {
6345 DisplayMoveError(_("It is White's turn"));
6350 case MachinePlaysBlack:
6351 /* User is moving for White */
6352 if (!WhiteOnMove(currentMove)) {
6353 DisplayMoveError(_("It is Black's turn"));
6360 case BeginningOfGame:
6363 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6364 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6365 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6366 /* User is moving for Black */
6367 if (WhiteOnMove(currentMove)) {
6368 DisplayMoveError(_("It is White's turn"));
6372 /* User is moving for White */
6373 if (!WhiteOnMove(currentMove)) {
6374 DisplayMoveError(_("It is Black's turn"));
6380 case IcsPlayingBlack:
6381 /* User is moving for Black */
6382 if (WhiteOnMove(currentMove)) {
6383 if (!appData.premove) {
6384 DisplayMoveError(_("It is White's turn"));
6385 } else if (toX >= 0 && toY >= 0) {
6388 premoveFromX = fromX;
6389 premoveFromY = fromY;
6390 premovePromoChar = promoChar;
6392 if (appData.debugMode)
6393 fprintf(debugFP, "Got premove: fromX %d,"
6394 "fromY %d, toX %d, toY %d\n",
6395 fromX, fromY, toX, toY);
6401 case IcsPlayingWhite:
6402 /* User is moving for White */
6403 if (!WhiteOnMove(currentMove)) {
6404 if (!appData.premove) {
6405 DisplayMoveError(_("It is Black's turn"));
6406 } else if (toX >= 0 && toY >= 0) {
6409 premoveFromX = fromX;
6410 premoveFromY = fromY;
6411 premovePromoChar = promoChar;
6413 if (appData.debugMode)
6414 fprintf(debugFP, "Got premove: fromX %d,"
6415 "fromY %d, toX %d, toY %d\n",
6416 fromX, fromY, toX, toY);
6426 /* EditPosition, empty square, or different color piece;
6427 click-click move is possible */
6428 if (toX == -2 || toY == -2) {
6429 boards[0][fromY][fromX] = EmptySquare;
6430 DrawPosition(FALSE, boards[currentMove]);
6432 } else if (toX >= 0 && toY >= 0) {
6433 boards[0][toY][toX] = boards[0][fromY][fromX];
6434 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6435 if(boards[0][fromY][0] != EmptySquare) {
6436 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6437 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6440 if(fromX == BOARD_RGHT+1) {
6441 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6442 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6443 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6446 boards[0][fromY][fromX] = EmptySquare;
6447 DrawPosition(FALSE, boards[currentMove]);
6453 if(toX < 0 || toY < 0) return;
6454 pdown = boards[currentMove][fromY][fromX];
6455 pup = boards[currentMove][toY][toX];
6457 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6458 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6459 if( pup != EmptySquare ) return;
6460 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6461 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6462 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6463 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6464 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6465 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6466 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6470 /* [HGM] always test for legality, to get promotion info */
6471 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6472 fromY, fromX, toY, toX, promoChar);
6473 /* [HGM] but possibly ignore an IllegalMove result */
6474 if (appData.testLegality) {
6475 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6476 DisplayMoveError(_("Illegal move"));
6481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6484 /* Common tail of UserMoveEvent and DropMenuEvent */
6486 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6488 int fromX, fromY, toX, toY;
6489 /*char*/int promoChar;
6493 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6494 // [HGM] superchess: suppress promotions to non-available piece
6495 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6496 if(WhiteOnMove(currentMove)) {
6497 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6499 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6503 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6504 move type in caller when we know the move is a legal promotion */
6505 if(moveType == NormalMove && promoChar)
6506 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6508 /* [HGM] <popupFix> The following if has been moved here from
6509 UserMoveEvent(). Because it seemed to belong here (why not allow
6510 piece drops in training games?), and because it can only be
6511 performed after it is known to what we promote. */
6512 if (gameMode == Training) {
6513 /* compare the move played on the board to the next move in the
6514 * game. If they match, display the move and the opponent's response.
6515 * If they don't match, display an error message.
6519 CopyBoard(testBoard, boards[currentMove]);
6520 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6522 if (CompareBoards(testBoard, boards[currentMove+1])) {
6523 ForwardInner(currentMove+1);
6525 /* Autoplay the opponent's response.
6526 * if appData.animate was TRUE when Training mode was entered,
6527 * the response will be animated.
6529 saveAnimate = appData.animate;
6530 appData.animate = animateTraining;
6531 ForwardInner(currentMove+1);
6532 appData.animate = saveAnimate;
6534 /* check for the end of the game */
6535 if (currentMove >= forwardMostMove) {
6536 gameMode = PlayFromGameFile;
6538 SetTrainingModeOff();
6539 DisplayInformation(_("End of game"));
6542 DisplayError(_("Incorrect move"), 0);
6547 /* Ok, now we know that the move is good, so we can kill
6548 the previous line in Analysis Mode */
6549 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6550 && currentMove < forwardMostMove) {
6551 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6552 else forwardMostMove = currentMove;
6555 /* If we need the chess program but it's dead, restart it */
6556 ResurrectChessProgram();
6558 /* A user move restarts a paused game*/
6562 thinkOutput[0] = NULLCHAR;
6564 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6566 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6567 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6571 if (gameMode == BeginningOfGame) {
6572 if (appData.noChessProgram) {
6573 gameMode = EditGame;
6577 gameMode = MachinePlaysBlack;
6580 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6582 if (first.sendName) {
6583 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6584 SendToProgram(buf, &first);
6591 /* Relay move to ICS or chess engine */
6592 if (appData.icsActive) {
6593 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6594 gameMode == IcsExamining) {
6595 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6596 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6598 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6600 // also send plain move, in case ICS does not understand atomic claims
6601 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6605 if (first.sendTime && (gameMode == BeginningOfGame ||
6606 gameMode == MachinePlaysWhite ||
6607 gameMode == MachinePlaysBlack)) {
6608 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6610 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6611 // [HGM] book: if program might be playing, let it use book
6612 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6613 first.maybeThinking = TRUE;
6614 } else SendMoveToProgram(forwardMostMove-1, &first);
6615 if (currentMove == cmailOldMove + 1) {
6616 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6620 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624 if(appData.testLegality)
6625 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6631 if (WhiteOnMove(currentMove)) {
6632 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6634 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6638 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6643 case MachinePlaysBlack:
6644 case MachinePlaysWhite:
6645 /* disable certain menu options while machine is thinking */
6646 SetMachineThinkingEnables();
6653 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6654 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6656 if(bookHit) { // [HGM] book: simulate book reply
6657 static char bookMove[MSG_SIZ]; // a bit generous?
6659 programStats.nodes = programStats.depth = programStats.time =
6660 programStats.score = programStats.got_only_move = 0;
6661 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6663 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6664 strcat(bookMove, bookHit);
6665 HandleMachineMove(bookMove, &first);
6671 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6678 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6679 Markers *m = (Markers *) closure;
6680 if(rf == fromY && ff == fromX)
6681 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6682 || kind == WhiteCapturesEnPassant
6683 || kind == BlackCapturesEnPassant);
6684 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6688 MarkTargetSquares(int clear)
6691 if(!appData.markers || !appData.highlightDragging ||
6692 !appData.testLegality || gameMode == EditPosition) return;
6694 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6697 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6698 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6699 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6701 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6704 DrawPosition(TRUE, NULL);
6708 Explode(Board board, int fromX, int fromY, int toX, int toY)
6710 if(gameInfo.variant == VariantAtomic &&
6711 (board[toY][toX] != EmptySquare || // capture?
6712 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6713 board[fromY][fromX] == BlackPawn )
6715 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6721 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6723 int CanPromote(ChessSquare piece, int y)
6725 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6726 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6727 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6728 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6729 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6730 gameInfo.variant == VariantMakruk) return FALSE;
6731 return (piece == BlackPawn && y == 1 ||
6732 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6733 piece == BlackLance && y == 1 ||
6734 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6737 void LeftClick(ClickType clickType, int xPix, int yPix)
6740 Boolean saveAnimate;
6741 static int second = 0, promotionChoice = 0, clearFlag = 0;
6742 char promoChoice = NULLCHAR;
6745 if(appData.seekGraph && appData.icsActive && loggedOn &&
6746 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6747 SeekGraphClick(clickType, xPix, yPix, 0);
6751 if (clickType == Press) ErrorPopDown();
6752 MarkTargetSquares(1);
6754 x = EventToSquare(xPix, BOARD_WIDTH);
6755 y = EventToSquare(yPix, BOARD_HEIGHT);
6756 if (!flipView && y >= 0) {
6757 y = BOARD_HEIGHT - 1 - y;
6759 if (flipView && x >= 0) {
6760 x = BOARD_WIDTH - 1 - x;
6763 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6764 defaultPromoChoice = promoSweep;
6765 promoSweep = EmptySquare; // terminate sweep
6766 promoDefaultAltered = TRUE;
6767 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6770 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6771 if(clickType == Release) return; // ignore upclick of click-click destination
6772 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6773 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6774 if(gameInfo.holdingsWidth &&
6775 (WhiteOnMove(currentMove)
6776 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6777 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6778 // click in right holdings, for determining promotion piece
6779 ChessSquare p = boards[currentMove][y][x];
6780 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6781 if(p != EmptySquare) {
6782 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6787 DrawPosition(FALSE, boards[currentMove]);
6791 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6792 if(clickType == Press
6793 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6794 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6795 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6798 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6799 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6801 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6802 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6803 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6804 defaultPromoChoice = DefaultPromoChoice(side);
6807 autoQueen = appData.alwaysPromoteToQueen;
6811 gatingPiece = EmptySquare;
6812 if (clickType != Press) {
6813 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6814 DragPieceEnd(xPix, yPix); dragging = 0;
6815 DrawPosition(FALSE, NULL);
6819 fromX = x; fromY = y;
6820 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6821 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6822 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6824 if (OKToStartUserMove(fromX, fromY)) {
6826 MarkTargetSquares(0);
6827 DragPieceBegin(xPix, yPix); dragging = 1;
6828 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6829 promoSweep = defaultPromoChoice;
6830 selectFlag = 0; lastX = xPix; lastY = yPix;
6831 Sweep(0); // Pawn that is going to promote: preview promotion piece
6832 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6834 if (appData.highlightDragging) {
6835 SetHighlights(fromX, fromY, -1, -1);
6837 } else fromX = fromY = -1;
6843 if (clickType == Press && gameMode != EditPosition) {
6848 // ignore off-board to clicks
6849 if(y < 0 || x < 0) return;
6851 /* Check if clicking again on the same color piece */
6852 fromP = boards[currentMove][fromY][fromX];
6853 toP = boards[currentMove][y][x];
6854 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6855 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6856 WhitePawn <= toP && toP <= WhiteKing &&
6857 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6858 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6859 (BlackPawn <= fromP && fromP <= BlackKing &&
6860 BlackPawn <= toP && toP <= BlackKing &&
6861 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6862 !(fromP == BlackKing && toP == BlackRook && frc))) {
6863 /* Clicked again on same color piece -- changed his mind */
6864 second = (x == fromX && y == fromY);
6865 promoDefaultAltered = FALSE;
6866 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6867 if (appData.highlightDragging) {
6868 SetHighlights(x, y, -1, -1);
6872 if (OKToStartUserMove(x, y)) {
6873 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6874 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6875 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6876 gatingPiece = boards[currentMove][fromY][fromX];
6877 else gatingPiece = EmptySquare;
6879 fromY = y; dragging = 1;
6880 MarkTargetSquares(0);
6881 DragPieceBegin(xPix, yPix);
6882 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6883 promoSweep = defaultPromoChoice;
6884 selectFlag = 0; lastX = xPix; lastY = yPix;
6885 Sweep(0); // Pawn that is going to promote: preview promotion piece
6889 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6892 // ignore clicks on holdings
6893 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6896 if (clickType == Release && x == fromX && y == fromY) {
6897 DragPieceEnd(xPix, yPix); dragging = 0;
6899 // a deferred attempt to click-click move an empty square on top of a piece
6900 boards[currentMove][y][x] = EmptySquare;
6902 DrawPosition(FALSE, boards[currentMove]);
6903 fromX = fromY = -1; clearFlag = 0;
6906 if (appData.animateDragging) {
6907 /* Undo animation damage if any */
6908 DrawPosition(FALSE, NULL);
6911 /* Second up/down in same square; just abort move */
6914 gatingPiece = EmptySquare;
6917 ClearPremoveHighlights();
6919 /* First upclick in same square; start click-click mode */
6920 SetHighlights(x, y, -1, -1);
6927 /* we now have a different from- and (possibly off-board) to-square */
6928 /* Completed move */
6931 saveAnimate = appData.animate;
6932 if (clickType == Press) {
6933 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6934 // must be Edit Position mode with empty-square selected
6935 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6936 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6939 /* Finish clickclick move */
6940 if (appData.animate || appData.highlightLastMove) {
6941 SetHighlights(fromX, fromY, toX, toY);
6946 /* Finish drag move */
6947 if (appData.highlightLastMove) {
6948 SetHighlights(fromX, fromY, toX, toY);
6952 DragPieceEnd(xPix, yPix); dragging = 0;
6953 /* Don't animate move and drag both */
6954 appData.animate = FALSE;
6957 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6958 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6959 ChessSquare piece = boards[currentMove][fromY][fromX];
6960 if(gameMode == EditPosition && piece != EmptySquare &&
6961 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6964 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6965 n = PieceToNumber(piece - (int)BlackPawn);
6966 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6967 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6968 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6970 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6971 n = PieceToNumber(piece);
6972 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6973 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6974 boards[currentMove][n][BOARD_WIDTH-2]++;
6976 boards[currentMove][fromY][fromX] = EmptySquare;
6980 DrawPosition(TRUE, boards[currentMove]);
6984 // off-board moves should not be highlighted
6985 if(x < 0 || y < 0) ClearHighlights();
6987 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6989 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6990 SetHighlights(fromX, fromY, toX, toY);
6991 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6992 // [HGM] super: promotion to captured piece selected from holdings
6993 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6994 promotionChoice = TRUE;
6995 // kludge follows to temporarily execute move on display, without promoting yet
6996 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6997 boards[currentMove][toY][toX] = p;
6998 DrawPosition(FALSE, boards[currentMove]);
6999 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7000 boards[currentMove][toY][toX] = q;
7001 DisplayMessage("Click in holdings to choose piece", "");
7006 int oldMove = currentMove;
7007 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7008 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7009 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7010 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7011 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7012 DrawPosition(TRUE, boards[currentMove]);
7015 appData.animate = saveAnimate;
7016 if (appData.animate || appData.animateDragging) {
7017 /* Undo animation damage if needed */
7018 DrawPosition(FALSE, NULL);
7022 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7023 { // front-end-free part taken out of PieceMenuPopup
7024 int whichMenu; int xSqr, ySqr;
7026 if(seekGraphUp) { // [HGM] seekgraph
7027 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7028 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7032 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7033 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7034 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7035 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7036 if(action == Press) {
7037 originalFlip = flipView;
7038 flipView = !flipView; // temporarily flip board to see game from partners perspective
7039 DrawPosition(TRUE, partnerBoard);
7040 DisplayMessage(partnerStatus, "");
7042 } else if(action == Release) {
7043 flipView = originalFlip;
7044 DrawPosition(TRUE, boards[currentMove]);
7050 xSqr = EventToSquare(x, BOARD_WIDTH);
7051 ySqr = EventToSquare(y, BOARD_HEIGHT);
7052 if (action == Release) {
7053 if(pieceSweep != EmptySquare) {
7054 EditPositionMenuEvent(pieceSweep, toX, toY);
7055 pieceSweep = EmptySquare;
7056 } else UnLoadPV(); // [HGM] pv
7058 if (action != Press) return -2; // return code to be ignored
7061 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7063 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7064 if (xSqr < 0 || ySqr < 0) return -1;
7065 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7066 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7067 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7068 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7072 if(!appData.icsEngineAnalyze) return -1;
7073 case IcsPlayingWhite:
7074 case IcsPlayingBlack:
7075 if(!appData.zippyPlay) goto noZip;
7078 case MachinePlaysWhite:
7079 case MachinePlaysBlack:
7080 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7081 if (!appData.dropMenu) {
7083 return 2; // flag front-end to grab mouse events
7085 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7086 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7089 if (xSqr < 0 || ySqr < 0) return -1;
7090 if (!appData.dropMenu || appData.testLegality &&
7091 gameInfo.variant != VariantBughouse &&
7092 gameInfo.variant != VariantCrazyhouse) return -1;
7093 whichMenu = 1; // drop menu
7099 if (((*fromX = xSqr) < 0) ||
7100 ((*fromY = ySqr) < 0)) {
7101 *fromX = *fromY = -1;
7105 *fromX = BOARD_WIDTH - 1 - *fromX;
7107 *fromY = BOARD_HEIGHT - 1 - *fromY;
7112 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7114 // char * hint = lastHint;
7115 FrontEndProgramStats stats;
7117 stats.which = cps == &first ? 0 : 1;
7118 stats.depth = cpstats->depth;
7119 stats.nodes = cpstats->nodes;
7120 stats.score = cpstats->score;
7121 stats.time = cpstats->time;
7122 stats.pv = cpstats->movelist;
7123 stats.hint = lastHint;
7124 stats.an_move_index = 0;
7125 stats.an_move_count = 0;
7127 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7128 stats.hint = cpstats->move_name;
7129 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7130 stats.an_move_count = cpstats->nr_moves;
7133 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
7135 SetProgramStats( &stats );
7138 #define MAXPLAYERS 500
7141 TourneyStandings(int display)
7143 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7144 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7145 char result, *p, *names[MAXPLAYERS];
7147 names[0] = p = strdup(appData.participants);
7148 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7150 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7152 while(result = appData.results[nr]) {
7153 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7154 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7155 wScore = bScore = 0;
7157 case '+': wScore = 2; break;
7158 case '-': bScore = 2; break;
7159 case '=': wScore = bScore = 1; break;
7161 case '*': return strdup("busy"); // tourney not finished
7169 if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7170 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7171 for(w=0; w<nPlayers; w++) {
7173 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7174 ranking[w] = b; points[w] = bScore; score[b] = -2;
7176 p = malloc(nPlayers*34+1);
7177 for(w=0; w<nPlayers && w<display; w++)
7178 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7184 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7185 { // count all piece types
7187 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7188 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7189 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7192 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7193 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7194 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7195 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7196 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7197 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7202 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7204 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7205 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7207 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7208 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7209 if(myPawns == 2 && nMine == 3) // KPP
7210 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7211 if(myPawns == 1 && nMine == 2) // KP
7212 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7213 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7214 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7215 if(myPawns) return FALSE;
7216 if(pCnt[WhiteRook+side])
7217 return pCnt[BlackRook-side] ||
7218 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7219 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7220 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7221 if(pCnt[WhiteCannon+side]) {
7222 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7223 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7225 if(pCnt[WhiteKnight+side])
7226 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7231 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7233 VariantClass v = gameInfo.variant;
7235 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7236 if(v == VariantShatranj) return TRUE; // always winnable through baring
7237 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7238 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7240 if(v == VariantXiangqi) {
7241 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7243 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7244 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7245 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7246 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7247 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7248 if(stale) // we have at least one last-rank P plus perhaps C
7249 return majors // KPKX
7250 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7252 return pCnt[WhiteFerz+side] // KCAK
7253 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7254 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7255 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7257 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7258 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7260 if(nMine == 1) return FALSE; // bare King
7261 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
7262 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7263 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7264 // by now we have King + 1 piece (or multiple Bishops on the same color)
7265 if(pCnt[WhiteKnight+side])
7266 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7267 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7268 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7270 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7271 if(pCnt[WhiteAlfil+side])
7272 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7273 if(pCnt[WhiteWazir+side])
7274 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7281 Adjudicate(ChessProgramState *cps)
7282 { // [HGM] some adjudications useful with buggy engines
7283 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7284 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7285 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7286 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7287 int k, count = 0; static int bare = 1;
7288 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7289 Boolean canAdjudicate = !appData.icsActive;
7291 // most tests only when we understand the game, i.e. legality-checking on
7292 if( appData.testLegality )
7293 { /* [HGM] Some more adjudications for obstinate engines */
7294 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7295 static int moveCount = 6;
7297 char *reason = NULL;
7299 /* Count what is on board. */
7300 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7302 /* Some material-based adjudications that have to be made before stalemate test */
7303 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7304 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7305 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7306 if(canAdjudicate && appData.checkMates) {
7308 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7309 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7310 "Xboard adjudication: King destroyed", GE_XBOARD );
7315 /* Bare King in Shatranj (loses) or Losers (wins) */
7316 if( nrW == 1 || nrB == 1) {
7317 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7318 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7319 if(canAdjudicate && appData.checkMates) {
7321 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7322 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7323 "Xboard adjudication: Bare king", GE_XBOARD );
7327 if( gameInfo.variant == VariantShatranj && --bare < 0)
7329 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7330 if(canAdjudicate && appData.checkMates) {
7331 /* but only adjudicate if adjudication enabled */
7333 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7334 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7335 "Xboard adjudication: Bare king", GE_XBOARD );
7342 // don't wait for engine to announce game end if we can judge ourselves
7343 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7345 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7346 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7347 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7348 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7351 reason = "Xboard adjudication: 3rd check";
7352 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7362 reason = "Xboard adjudication: Stalemate";
7363 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7364 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7365 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7366 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7367 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7368 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7369 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7370 EP_CHECKMATE : EP_WINS);
7371 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7372 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7376 reason = "Xboard adjudication: Checkmate";
7377 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7381 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7383 result = GameIsDrawn; break;
7385 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7387 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7391 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7393 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7394 GameEnds( result, reason, GE_XBOARD );
7398 /* Next absolutely insufficient mating material. */
7399 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7400 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7401 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7403 /* always flag draws, for judging claims */
7404 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7406 if(canAdjudicate && appData.materialDraws) {
7407 /* but only adjudicate them if adjudication enabled */
7408 if(engineOpponent) {
7409 SendToProgram("force\n", engineOpponent); // suppress reply
7410 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7412 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7417 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7418 if(gameInfo.variant == VariantXiangqi ?
7419 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7421 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7422 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7423 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7424 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7426 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7427 { /* if the first 3 moves do not show a tactical win, declare draw */
7428 if(engineOpponent) {
7429 SendToProgram("force\n", engineOpponent); // suppress reply
7430 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7432 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7435 } else moveCount = 6;
7437 if (appData.debugMode) { int i;
7438 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7439 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7440 appData.drawRepeats);
7441 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7442 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7446 // Repetition draws and 50-move rule can be applied independently of legality testing
7448 /* Check for rep-draws */
7450 for(k = forwardMostMove-2;
7451 k>=backwardMostMove && k>=forwardMostMove-100 &&
7452 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7453 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7456 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7457 /* compare castling rights */
7458 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7459 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7460 rights++; /* King lost rights, while rook still had them */
7461 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7462 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7463 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7464 rights++; /* but at least one rook lost them */
7466 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7467 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7469 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7470 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7471 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7474 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7475 && appData.drawRepeats > 1) {
7476 /* adjudicate after user-specified nr of repeats */
7477 int result = GameIsDrawn;
7478 char *details = "XBoard adjudication: repetition draw";
7479 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7480 // [HGM] xiangqi: check for forbidden perpetuals
7481 int m, ourPerpetual = 1, hisPerpetual = 1;
7482 for(m=forwardMostMove; m>k; m-=2) {
7483 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7484 ourPerpetual = 0; // the current mover did not always check
7485 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7486 hisPerpetual = 0; // the opponent did not always check
7488 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7489 ourPerpetual, hisPerpetual);
7490 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7491 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7492 details = "Xboard adjudication: perpetual checking";
7494 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7495 break; // (or we would have caught him before). Abort repetition-checking loop.
7497 // Now check for perpetual chases
7498 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7499 hisPerpetual = PerpetualChase(k, forwardMostMove);
7500 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7501 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7502 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7503 details = "Xboard adjudication: perpetual chasing";
7505 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7506 break; // Abort repetition-checking loop.
7508 // if neither of us is checking or chasing all the time, or both are, it is draw
7510 if(engineOpponent) {
7511 SendToProgram("force\n", engineOpponent); // suppress reply
7512 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7514 GameEnds( result, details, GE_XBOARD );
7517 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7518 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7522 /* Now we test for 50-move draws. Determine ply count */
7523 count = forwardMostMove;
7524 /* look for last irreversble move */
7525 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7527 /* if we hit starting position, add initial plies */
7528 if( count == backwardMostMove )
7529 count -= initialRulePlies;
7530 count = forwardMostMove - count;
7531 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7532 // adjust reversible move counter for checks in Xiangqi
7533 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7534 if(i < backwardMostMove) i = backwardMostMove;
7535 while(i <= forwardMostMove) {
7536 lastCheck = inCheck; // check evasion does not count
7537 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7538 if(inCheck || lastCheck) count--; // check does not count
7543 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7544 /* this is used to judge if draw claims are legal */
7545 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7546 if(engineOpponent) {
7547 SendToProgram("force\n", engineOpponent); // suppress reply
7548 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7550 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7554 /* if draw offer is pending, treat it as a draw claim
7555 * when draw condition present, to allow engines a way to
7556 * claim draws before making their move to avoid a race
7557 * condition occurring after their move
7559 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7561 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7562 p = "Draw claim: 50-move rule";
7563 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7564 p = "Draw claim: 3-fold repetition";
7565 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7566 p = "Draw claim: insufficient mating material";
7567 if( p != NULL && canAdjudicate) {
7568 if(engineOpponent) {
7569 SendToProgram("force\n", engineOpponent); // suppress reply
7570 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7572 GameEnds( GameIsDrawn, p, GE_XBOARD );
7577 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7578 if(engineOpponent) {
7579 SendToProgram("force\n", engineOpponent); // suppress reply
7580 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7582 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7588 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7589 { // [HGM] book: this routine intercepts moves to simulate book replies
7590 char *bookHit = NULL;
7592 //first determine if the incoming move brings opponent into his book
7593 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7594 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7595 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7596 if(bookHit != NULL && !cps->bookSuspend) {
7597 // make sure opponent is not going to reply after receiving move to book position
7598 SendToProgram("force\n", cps);
7599 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7601 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7602 // now arrange restart after book miss
7604 // after a book hit we never send 'go', and the code after the call to this routine
7605 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7606 char buf[MSG_SIZ], *move = bookHit;
7608 int fromX, fromY, toX, toY;
7612 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7613 &fromX, &fromY, &toX, &toY, &promoChar)) {
7614 (void) CoordsToAlgebraic(boards[forwardMostMove],
7615 PosFlags(forwardMostMove),
7616 fromY, fromX, toY, toX, promoChar, move);
7618 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7622 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7623 SendToProgram(buf, cps);
7624 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7625 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7626 SendToProgram("go\n", cps);
7627 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7628 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7629 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7630 SendToProgram("go\n", cps);
7631 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7633 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7637 ChessProgramState *savedState;
7638 void DeferredBookMove(void)
7640 if(savedState->lastPing != savedState->lastPong)
7641 ScheduleDelayedEvent(DeferredBookMove, 10);
7643 HandleMachineMove(savedMessage, savedState);
7646 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7649 HandleMachineMove(message, cps)
7651 ChessProgramState *cps;
7653 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7654 char realname[MSG_SIZ];
7655 int fromX, fromY, toX, toY;
7662 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7663 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7664 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7665 pairingReceived = 1;
7667 return; // Skim the pairing messages here.
7672 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7674 * Kludge to ignore BEL characters
7676 while (*message == '\007') message++;
7679 * [HGM] engine debug message: ignore lines starting with '#' character
7681 if(cps->debug && *message == '#') return;
7684 * Look for book output
7686 if (cps == &first && bookRequested) {
7687 if (message[0] == '\t' || message[0] == ' ') {
7688 /* Part of the book output is here; append it */
7689 strcat(bookOutput, message);
7690 strcat(bookOutput, " \n");
7692 } else if (bookOutput[0] != NULLCHAR) {
7693 /* All of book output has arrived; display it */
7694 char *p = bookOutput;
7695 while (*p != NULLCHAR) {
7696 if (*p == '\t') *p = ' ';
7699 DisplayInformation(bookOutput);
7700 bookRequested = FALSE;
7701 /* Fall through to parse the current output */
7706 * Look for machine move.
7708 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7709 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7711 /* This method is only useful on engines that support ping */
7712 if (cps->lastPing != cps->lastPong) {
7713 if (gameMode == BeginningOfGame) {
7714 /* Extra move from before last new; ignore */
7715 if (appData.debugMode) {
7716 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7719 if (appData.debugMode) {
7720 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7721 cps->which, gameMode);
7724 SendToProgram("undo\n", cps);
7730 case BeginningOfGame:
7731 /* Extra move from before last reset; ignore */
7732 if (appData.debugMode) {
7733 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7740 /* Extra move after we tried to stop. The mode test is
7741 not a reliable way of detecting this problem, but it's
7742 the best we can do on engines that don't support ping.
7744 if (appData.debugMode) {
7745 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7746 cps->which, gameMode);
7748 SendToProgram("undo\n", cps);
7751 case MachinePlaysWhite:
7752 case IcsPlayingWhite:
7753 machineWhite = TRUE;
7756 case MachinePlaysBlack:
7757 case IcsPlayingBlack:
7758 machineWhite = FALSE;
7761 case TwoMachinesPlay:
7762 machineWhite = (cps->twoMachinesColor[0] == 'w');
7765 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7766 if (appData.debugMode) {
7768 "Ignoring move out of turn by %s, gameMode %d"
7769 ", forwardMost %d\n",
7770 cps->which, gameMode, forwardMostMove);
7775 if (appData.debugMode) { int f = forwardMostMove;
7776 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7777 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7778 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7780 if(cps->alphaRank) AlphaRank(machineMove, 4);
7781 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7782 &fromX, &fromY, &toX, &toY, &promoChar)) {
7783 /* Machine move could not be parsed; ignore it. */
7784 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7785 machineMove, _(cps->which));
7786 DisplayError(buf1, 0);
7787 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7788 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7789 if (gameMode == TwoMachinesPlay) {
7790 GameEnds(machineWhite ? BlackWins : WhiteWins,
7796 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7797 /* So we have to redo legality test with true e.p. status here, */
7798 /* to make sure an illegal e.p. capture does not slip through, */
7799 /* to cause a forfeit on a justified illegal-move complaint */
7800 /* of the opponent. */
7801 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7803 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7804 fromY, fromX, toY, toX, promoChar);
7805 if (appData.debugMode) {
7807 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7808 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7809 fprintf(debugFP, "castling rights\n");
7811 if(moveType == IllegalMove) {
7812 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7813 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7814 GameEnds(machineWhite ? BlackWins : WhiteWins,
7817 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7818 /* [HGM] Kludge to handle engines that send FRC-style castling
7819 when they shouldn't (like TSCP-Gothic) */
7821 case WhiteASideCastleFR:
7822 case BlackASideCastleFR:
7824 currentMoveString[2]++;
7826 case WhiteHSideCastleFR:
7827 case BlackHSideCastleFR:
7829 currentMoveString[2]--;
7831 default: ; // nothing to do, but suppresses warning of pedantic compilers
7834 hintRequested = FALSE;
7835 lastHint[0] = NULLCHAR;
7836 bookRequested = FALSE;
7837 /* Program may be pondering now */
7838 cps->maybeThinking = TRUE;
7839 if (cps->sendTime == 2) cps->sendTime = 1;
7840 if (cps->offeredDraw) cps->offeredDraw--;
7842 /* [AS] Save move info*/
7843 pvInfoList[ forwardMostMove ].score = programStats.score;
7844 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7845 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7847 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7849 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7850 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7853 while( count < adjudicateLossPlies ) {
7854 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7857 score = -score; /* Flip score for winning side */
7860 if( score > adjudicateLossThreshold ) {
7867 if( count >= adjudicateLossPlies ) {
7868 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7870 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7871 "Xboard adjudication",
7878 if(Adjudicate(cps)) {
7879 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7880 return; // [HGM] adjudicate: for all automatic game ends
7884 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7886 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7887 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7889 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7891 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7893 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7894 char buf[3*MSG_SIZ];
7896 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7897 programStats.score / 100.,
7899 programStats.time / 100.,
7900 (unsigned int)programStats.nodes,
7901 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7902 programStats.movelist);
7904 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7909 /* [AS] Clear stats for next move */
7910 ClearProgramStats();
7911 thinkOutput[0] = NULLCHAR;
7912 hiddenThinkOutputState = 0;
7915 if (gameMode == TwoMachinesPlay) {
7916 /* [HGM] relaying draw offers moved to after reception of move */
7917 /* and interpreting offer as claim if it brings draw condition */
7918 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7919 SendToProgram("draw\n", cps->other);
7921 if (cps->other->sendTime) {
7922 SendTimeRemaining(cps->other,
7923 cps->other->twoMachinesColor[0] == 'w');
7925 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7926 if (firstMove && !bookHit) {
7928 if (cps->other->useColors) {
7929 SendToProgram(cps->other->twoMachinesColor, cps->other);
7931 SendToProgram("go\n", cps->other);
7933 cps->other->maybeThinking = TRUE;
7936 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7938 if (!pausing && appData.ringBellAfterMoves) {
7943 * Reenable menu items that were disabled while
7944 * machine was thinking
7946 if (gameMode != TwoMachinesPlay)
7947 SetUserThinkingEnables();
7949 // [HGM] book: after book hit opponent has received move and is now in force mode
7950 // force the book reply into it, and then fake that it outputted this move by jumping
7951 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7953 static char bookMove[MSG_SIZ]; // a bit generous?
7955 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7956 strcat(bookMove, bookHit);
7959 programStats.nodes = programStats.depth = programStats.time =
7960 programStats.score = programStats.got_only_move = 0;
7961 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7963 if(cps->lastPing != cps->lastPong) {
7964 savedMessage = message; // args for deferred call
7966 ScheduleDelayedEvent(DeferredBookMove, 10);
7975 /* Set special modes for chess engines. Later something general
7976 * could be added here; for now there is just one kludge feature,
7977 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7978 * when "xboard" is given as an interactive command.
7980 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7981 cps->useSigint = FALSE;
7982 cps->useSigterm = FALSE;
7984 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7985 ParseFeatures(message+8, cps);
7986 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7989 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7990 int dummy, s=6; char buf[MSG_SIZ];
7991 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7992 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7993 ParseFEN(boards[0], &dummy, message+s);
7994 DrawPosition(TRUE, boards[0]);
7995 startedFromSetupPosition = TRUE;
7998 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7999 * want this, I was asked to put it in, and obliged.
8001 if (!strncmp(message, "setboard ", 9)) {
8002 Board initial_position;
8004 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8006 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8007 DisplayError(_("Bad FEN received from engine"), 0);
8011 CopyBoard(boards[0], initial_position);
8012 initialRulePlies = FENrulePlies;
8013 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8014 else gameMode = MachinePlaysBlack;
8015 DrawPosition(FALSE, boards[currentMove]);
8021 * Look for communication commands
8023 if (!strncmp(message, "telluser ", 9)) {
8024 if(message[9] == '\\' && message[10] == '\\')
8025 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8026 DisplayNote(message + 9);
8029 if (!strncmp(message, "tellusererror ", 14)) {
8031 if(message[14] == '\\' && message[15] == '\\')
8032 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8033 DisplayError(message + 14, 0);
8036 if (!strncmp(message, "tellopponent ", 13)) {
8037 if (appData.icsActive) {
8039 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8043 DisplayNote(message + 13);
8047 if (!strncmp(message, "tellothers ", 11)) {
8048 if (appData.icsActive) {
8050 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8056 if (!strncmp(message, "tellall ", 8)) {
8057 if (appData.icsActive) {
8059 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8063 DisplayNote(message + 8);
8067 if (strncmp(message, "warning", 7) == 0) {
8068 /* Undocumented feature, use tellusererror in new code */
8069 DisplayError(message, 0);
8072 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8073 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8074 strcat(realname, " query");
8075 AskQuestion(realname, buf2, buf1, cps->pr);
8078 /* Commands from the engine directly to ICS. We don't allow these to be
8079 * sent until we are logged on. Crafty kibitzes have been known to
8080 * interfere with the login process.
8083 if (!strncmp(message, "tellics ", 8)) {
8084 SendToICS(message + 8);
8088 if (!strncmp(message, "tellicsnoalias ", 15)) {
8089 SendToICS(ics_prefix);
8090 SendToICS(message + 15);
8094 /* The following are for backward compatibility only */
8095 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8096 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8097 SendToICS(ics_prefix);
8103 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8107 * If the move is illegal, cancel it and redraw the board.
8108 * Also deal with other error cases. Matching is rather loose
8109 * here to accommodate engines written before the spec.
8111 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8112 strncmp(message, "Error", 5) == 0) {
8113 if (StrStr(message, "name") ||
8114 StrStr(message, "rating") || StrStr(message, "?") ||
8115 StrStr(message, "result") || StrStr(message, "board") ||
8116 StrStr(message, "bk") || StrStr(message, "computer") ||
8117 StrStr(message, "variant") || StrStr(message, "hint") ||
8118 StrStr(message, "random") || StrStr(message, "depth") ||
8119 StrStr(message, "accepted")) {
8122 if (StrStr(message, "protover")) {
8123 /* Program is responding to input, so it's apparently done
8124 initializing, and this error message indicates it is
8125 protocol version 1. So we don't need to wait any longer
8126 for it to initialize and send feature commands. */
8127 FeatureDone(cps, 1);
8128 cps->protocolVersion = 1;
8131 cps->maybeThinking = FALSE;
8133 if (StrStr(message, "draw")) {
8134 /* Program doesn't have "draw" command */
8135 cps->sendDrawOffers = 0;
8138 if (cps->sendTime != 1 &&
8139 (StrStr(message, "time") || StrStr(message, "otim"))) {
8140 /* Program apparently doesn't have "time" or "otim" command */
8144 if (StrStr(message, "analyze")) {
8145 cps->analysisSupport = FALSE;
8146 cps->analyzing = FALSE;
8148 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8149 DisplayError(buf2, 0);
8152 if (StrStr(message, "(no matching move)st")) {
8153 /* Special kludge for GNU Chess 4 only */
8154 cps->stKludge = TRUE;
8155 SendTimeControl(cps, movesPerSession, timeControl,
8156 timeIncrement, appData.searchDepth,
8160 if (StrStr(message, "(no matching move)sd")) {
8161 /* Special kludge for GNU Chess 4 only */
8162 cps->sdKludge = TRUE;
8163 SendTimeControl(cps, movesPerSession, timeControl,
8164 timeIncrement, appData.searchDepth,
8168 if (!StrStr(message, "llegal")) {
8171 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8172 gameMode == IcsIdle) return;
8173 if (forwardMostMove <= backwardMostMove) return;
8174 if (pausing) PauseEvent();
8175 if(appData.forceIllegal) {
8176 // [HGM] illegal: machine refused move; force position after move into it
8177 SendToProgram("force\n", cps);
8178 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8179 // we have a real problem now, as SendBoard will use the a2a3 kludge
8180 // when black is to move, while there might be nothing on a2 or black
8181 // might already have the move. So send the board as if white has the move.
8182 // But first we must change the stm of the engine, as it refused the last move
8183 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8184 if(WhiteOnMove(forwardMostMove)) {
8185 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8186 SendBoard(cps, forwardMostMove); // kludgeless board
8188 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8189 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8190 SendBoard(cps, forwardMostMove+1); // kludgeless board
8192 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8193 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8194 gameMode == TwoMachinesPlay)
8195 SendToProgram("go\n", cps);
8198 if (gameMode == PlayFromGameFile) {
8199 /* Stop reading this game file */
8200 gameMode = EditGame;
8203 /* [HGM] illegal-move claim should forfeit game when Xboard */
8204 /* only passes fully legal moves */
8205 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8206 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8207 "False illegal-move claim", GE_XBOARD );
8208 return; // do not take back move we tested as valid
8210 currentMove = forwardMostMove-1;
8211 DisplayMove(currentMove-1); /* before DisplayMoveError */
8212 SwitchClocks(forwardMostMove-1); // [HGM] race
8213 DisplayBothClocks();
8214 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8215 parseList[currentMove], _(cps->which));
8216 DisplayMoveError(buf1);
8217 DrawPosition(FALSE, boards[currentMove]);
8220 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8221 /* Program has a broken "time" command that
8222 outputs a string not ending in newline.
8228 * If chess program startup fails, exit with an error message.
8229 * Attempts to recover here are futile.
8231 if ((StrStr(message, "unknown host") != NULL)
8232 || (StrStr(message, "No remote directory") != NULL)
8233 || (StrStr(message, "not found") != NULL)
8234 || (StrStr(message, "No such file") != NULL)
8235 || (StrStr(message, "can't alloc") != NULL)
8236 || (StrStr(message, "Permission denied") != NULL)) {
8238 cps->maybeThinking = FALSE;
8239 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8240 _(cps->which), cps->program, cps->host, message);
8241 RemoveInputSource(cps->isr);
8242 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8243 if(cps == &first) appData.noChessProgram = TRUE;
8244 DisplayError(buf1, 0);
8250 * Look for hint output
8252 if (sscanf(message, "Hint: %s", buf1) == 1) {
8253 if (cps == &first && hintRequested) {
8254 hintRequested = FALSE;
8255 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8256 &fromX, &fromY, &toX, &toY, &promoChar)) {
8257 (void) CoordsToAlgebraic(boards[forwardMostMove],
8258 PosFlags(forwardMostMove),
8259 fromY, fromX, toY, toX, promoChar, buf1);
8260 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8261 DisplayInformation(buf2);
8263 /* Hint move could not be parsed!? */
8264 snprintf(buf2, sizeof(buf2),
8265 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8266 buf1, _(cps->which));
8267 DisplayError(buf2, 0);
8270 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8276 * Ignore other messages if game is not in progress
8278 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8279 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8282 * look for win, lose, draw, or draw offer
8284 if (strncmp(message, "1-0", 3) == 0) {
8285 char *p, *q, *r = "";
8286 p = strchr(message, '{');
8294 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8296 } else if (strncmp(message, "0-1", 3) == 0) {
8297 char *p, *q, *r = "";
8298 p = strchr(message, '{');
8306 /* Kludge for Arasan 4.1 bug */
8307 if (strcmp(r, "Black resigns") == 0) {
8308 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8311 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8313 } else if (strncmp(message, "1/2", 3) == 0) {
8314 char *p, *q, *r = "";
8315 p = strchr(message, '{');
8324 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8327 } else if (strncmp(message, "White resign", 12) == 0) {
8328 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8330 } else if (strncmp(message, "Black resign", 12) == 0) {
8331 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8333 } else if (strncmp(message, "White matches", 13) == 0 ||
8334 strncmp(message, "Black matches", 13) == 0 ) {
8335 /* [HGM] ignore GNUShogi noises */
8337 } else if (strncmp(message, "White", 5) == 0 &&
8338 message[5] != '(' &&
8339 StrStr(message, "Black") == NULL) {
8340 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8342 } else if (strncmp(message, "Black", 5) == 0 &&
8343 message[5] != '(') {
8344 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8346 } else if (strcmp(message, "resign") == 0 ||
8347 strcmp(message, "computer resigns") == 0) {
8349 case MachinePlaysBlack:
8350 case IcsPlayingBlack:
8351 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8353 case MachinePlaysWhite:
8354 case IcsPlayingWhite:
8355 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8357 case TwoMachinesPlay:
8358 if (cps->twoMachinesColor[0] == 'w')
8359 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8361 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8368 } else if (strncmp(message, "opponent mates", 14) == 0) {
8370 case MachinePlaysBlack:
8371 case IcsPlayingBlack:
8372 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8374 case MachinePlaysWhite:
8375 case IcsPlayingWhite:
8376 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8378 case TwoMachinesPlay:
8379 if (cps->twoMachinesColor[0] == 'w')
8380 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8382 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8389 } else if (strncmp(message, "computer mates", 14) == 0) {
8391 case MachinePlaysBlack:
8392 case IcsPlayingBlack:
8393 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8395 case MachinePlaysWhite:
8396 case IcsPlayingWhite:
8397 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8399 case TwoMachinesPlay:
8400 if (cps->twoMachinesColor[0] == 'w')
8401 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8403 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8410 } else if (strncmp(message, "checkmate", 9) == 0) {
8411 if (WhiteOnMove(forwardMostMove)) {
8412 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8414 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8417 } else if (strstr(message, "Draw") != NULL ||
8418 strstr(message, "game is a draw") != NULL) {
8419 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8421 } else if (strstr(message, "offer") != NULL &&
8422 strstr(message, "draw") != NULL) {
8424 if (appData.zippyPlay && first.initDone) {
8425 /* Relay offer to ICS */
8426 SendToICS(ics_prefix);
8427 SendToICS("draw\n");
8430 cps->offeredDraw = 2; /* valid until this engine moves twice */
8431 if (gameMode == TwoMachinesPlay) {
8432 if (cps->other->offeredDraw) {
8433 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8434 /* [HGM] in two-machine mode we delay relaying draw offer */
8435 /* until after we also have move, to see if it is really claim */
8437 } else if (gameMode == MachinePlaysWhite ||
8438 gameMode == MachinePlaysBlack) {
8439 if (userOfferedDraw) {
8440 DisplayInformation(_("Machine accepts your draw offer"));
8441 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8443 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8450 * Look for thinking output
8452 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8453 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8455 int plylev, mvleft, mvtot, curscore, time;
8456 char mvname[MOVE_LEN];
8460 int prefixHint = FALSE;
8461 mvname[0] = NULLCHAR;
8464 case MachinePlaysBlack:
8465 case IcsPlayingBlack:
8466 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8468 case MachinePlaysWhite:
8469 case IcsPlayingWhite:
8470 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8475 case IcsObserving: /* [DM] icsEngineAnalyze */
8476 if (!appData.icsEngineAnalyze) ignore = TRUE;
8478 case TwoMachinesPlay:
8479 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8489 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8491 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8492 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8494 if (plyext != ' ' && plyext != '\t') {
8498 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8499 if( cps->scoreIsAbsolute &&
8500 ( gameMode == MachinePlaysBlack ||
8501 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8502 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8503 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8504 !WhiteOnMove(currentMove)
8507 curscore = -curscore;
8511 tempStats.depth = plylev;
8512 tempStats.nodes = nodes;
8513 tempStats.time = time;
8514 tempStats.score = curscore;
8515 tempStats.got_only_move = 0;
8517 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8520 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8521 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8522 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8523 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8524 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8525 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8526 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8527 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8530 /* Buffer overflow protection */
8531 if (buf1[0] != NULLCHAR) {
8532 if (strlen(buf1) >= sizeof(tempStats.movelist)
8533 && appData.debugMode) {
8535 "PV is too long; using the first %u bytes.\n",
8536 (unsigned) sizeof(tempStats.movelist) - 1);
8539 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8541 sprintf(tempStats.movelist, " no PV\n");
8544 if (tempStats.seen_stat) {
8545 tempStats.ok_to_send = 1;
8548 if (strchr(tempStats.movelist, '(') != NULL) {
8549 tempStats.line_is_book = 1;
8550 tempStats.nr_moves = 0;
8551 tempStats.moves_left = 0;
8553 tempStats.line_is_book = 0;
8556 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8557 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8559 SendProgramStatsToFrontend( cps, &tempStats );
8562 [AS] Protect the thinkOutput buffer from overflow... this
8563 is only useful if buf1 hasn't overflowed first!
8565 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8567 (gameMode == TwoMachinesPlay ?
8568 ToUpper(cps->twoMachinesColor[0]) : ' '),
8569 ((double) curscore) / 100.0,
8570 prefixHint ? lastHint : "",
8571 prefixHint ? " " : "" );
8573 if( buf1[0] != NULLCHAR ) {
8574 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8576 if( strlen(buf1) > max_len ) {
8577 if( appData.debugMode) {
8578 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8580 buf1[max_len+1] = '\0';
8583 strcat( thinkOutput, buf1 );
8586 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8587 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8588 DisplayMove(currentMove - 1);
8592 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8593 /* crafty (9.25+) says "(only move) <move>"
8594 * if there is only 1 legal move
8596 sscanf(p, "(only move) %s", buf1);
8597 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8598 sprintf(programStats.movelist, "%s (only move)", buf1);
8599 programStats.depth = 1;
8600 programStats.nr_moves = 1;
8601 programStats.moves_left = 1;
8602 programStats.nodes = 1;
8603 programStats.time = 1;
8604 programStats.got_only_move = 1;
8606 /* Not really, but we also use this member to
8607 mean "line isn't going to change" (Crafty
8608 isn't searching, so stats won't change) */
8609 programStats.line_is_book = 1;
8611 SendProgramStatsToFrontend( cps, &programStats );
8613 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8614 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8615 DisplayMove(currentMove - 1);
8618 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8619 &time, &nodes, &plylev, &mvleft,
8620 &mvtot, mvname) >= 5) {
8621 /* The stat01: line is from Crafty (9.29+) in response
8622 to the "." command */
8623 programStats.seen_stat = 1;
8624 cps->maybeThinking = TRUE;
8626 if (programStats.got_only_move || !appData.periodicUpdates)
8629 programStats.depth = plylev;
8630 programStats.time = time;
8631 programStats.nodes = nodes;
8632 programStats.moves_left = mvleft;
8633 programStats.nr_moves = mvtot;
8634 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8635 programStats.ok_to_send = 1;
8636 programStats.movelist[0] = '\0';
8638 SendProgramStatsToFrontend( cps, &programStats );
8642 } else if (strncmp(message,"++",2) == 0) {
8643 /* Crafty 9.29+ outputs this */
8644 programStats.got_fail = 2;
8647 } else if (strncmp(message,"--",2) == 0) {
8648 /* Crafty 9.29+ outputs this */
8649 programStats.got_fail = 1;
8652 } else if (thinkOutput[0] != NULLCHAR &&
8653 strncmp(message, " ", 4) == 0) {
8654 unsigned message_len;
8657 while (*p && *p == ' ') p++;
8659 message_len = strlen( p );
8661 /* [AS] Avoid buffer overflow */
8662 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8663 strcat(thinkOutput, " ");
8664 strcat(thinkOutput, p);
8667 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8668 strcat(programStats.movelist, " ");
8669 strcat(programStats.movelist, p);
8672 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8673 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8674 DisplayMove(currentMove - 1);
8682 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8683 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8685 ChessProgramStats cpstats;
8687 if (plyext != ' ' && plyext != '\t') {
8691 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8692 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8693 curscore = -curscore;
8696 cpstats.depth = plylev;
8697 cpstats.nodes = nodes;
8698 cpstats.time = time;
8699 cpstats.score = curscore;
8700 cpstats.got_only_move = 0;
8701 cpstats.movelist[0] = '\0';
8703 if (buf1[0] != NULLCHAR) {
8704 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8707 cpstats.ok_to_send = 0;
8708 cpstats.line_is_book = 0;
8709 cpstats.nr_moves = 0;
8710 cpstats.moves_left = 0;
8712 SendProgramStatsToFrontend( cps, &cpstats );
8719 /* Parse a game score from the character string "game", and
8720 record it as the history of the current game. The game
8721 score is NOT assumed to start from the standard position.
8722 The display is not updated in any way.
8725 ParseGameHistory(game)
8729 int fromX, fromY, toX, toY, boardIndex;
8734 if (appData.debugMode)
8735 fprintf(debugFP, "Parsing game history: %s\n", game);
8737 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8738 gameInfo.site = StrSave(appData.icsHost);
8739 gameInfo.date = PGNDate();
8740 gameInfo.round = StrSave("-");
8742 /* Parse out names of players */
8743 while (*game == ' ') game++;
8745 while (*game != ' ') *p++ = *game++;
8747 gameInfo.white = StrSave(buf);
8748 while (*game == ' ') game++;
8750 while (*game != ' ' && *game != '\n') *p++ = *game++;
8752 gameInfo.black = StrSave(buf);
8755 boardIndex = blackPlaysFirst ? 1 : 0;
8758 yyboardindex = boardIndex;
8759 moveType = (ChessMove) Myylex();
8761 case IllegalMove: /* maybe suicide chess, etc. */
8762 if (appData.debugMode) {
8763 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8764 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8765 setbuf(debugFP, NULL);
8767 case WhitePromotion:
8768 case BlackPromotion:
8769 case WhiteNonPromotion:
8770 case BlackNonPromotion:
8772 case WhiteCapturesEnPassant:
8773 case BlackCapturesEnPassant:
8774 case WhiteKingSideCastle:
8775 case WhiteQueenSideCastle:
8776 case BlackKingSideCastle:
8777 case BlackQueenSideCastle:
8778 case WhiteKingSideCastleWild:
8779 case WhiteQueenSideCastleWild:
8780 case BlackKingSideCastleWild:
8781 case BlackQueenSideCastleWild:
8783 case WhiteHSideCastleFR:
8784 case WhiteASideCastleFR:
8785 case BlackHSideCastleFR:
8786 case BlackASideCastleFR:
8788 fromX = currentMoveString[0] - AAA;
8789 fromY = currentMoveString[1] - ONE;
8790 toX = currentMoveString[2] - AAA;
8791 toY = currentMoveString[3] - ONE;
8792 promoChar = currentMoveString[4];
8796 fromX = moveType == WhiteDrop ?
8797 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8798 (int) CharToPiece(ToLower(currentMoveString[0]));
8800 toX = currentMoveString[2] - AAA;
8801 toY = currentMoveString[3] - ONE;
8802 promoChar = NULLCHAR;
8806 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8807 if (appData.debugMode) {
8808 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8809 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8810 setbuf(debugFP, NULL);
8812 DisplayError(buf, 0);
8814 case ImpossibleMove:
8816 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8817 if (appData.debugMode) {
8818 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8819 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8820 setbuf(debugFP, NULL);
8822 DisplayError(buf, 0);
8825 if (boardIndex < backwardMostMove) {
8826 /* Oops, gap. How did that happen? */
8827 DisplayError(_("Gap in move list"), 0);
8830 backwardMostMove = blackPlaysFirst ? 1 : 0;
8831 if (boardIndex > forwardMostMove) {
8832 forwardMostMove = boardIndex;
8836 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8837 strcat(parseList[boardIndex-1], " ");
8838 strcat(parseList[boardIndex-1], yy_text);
8850 case GameUnfinished:
8851 if (gameMode == IcsExamining) {
8852 if (boardIndex < backwardMostMove) {
8853 /* Oops, gap. How did that happen? */
8856 backwardMostMove = blackPlaysFirst ? 1 : 0;
8859 gameInfo.result = moveType;
8860 p = strchr(yy_text, '{');
8861 if (p == NULL) p = strchr(yy_text, '(');
8864 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8866 q = strchr(p, *p == '{' ? '}' : ')');
8867 if (q != NULL) *q = NULLCHAR;
8870 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8871 gameInfo.resultDetails = StrSave(p);
8874 if (boardIndex >= forwardMostMove &&
8875 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8876 backwardMostMove = blackPlaysFirst ? 1 : 0;
8879 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8880 fromY, fromX, toY, toX, promoChar,
8881 parseList[boardIndex]);
8882 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8883 /* currentMoveString is set as a side-effect of yylex */
8884 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8885 strcat(moveList[boardIndex], "\n");
8887 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8888 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8894 if(gameInfo.variant != VariantShogi)
8895 strcat(parseList[boardIndex - 1], "+");
8899 strcat(parseList[boardIndex - 1], "#");
8906 /* Apply a move to the given board */
8908 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8909 int fromX, fromY, toX, toY;
8913 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8914 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8916 /* [HGM] compute & store e.p. status and castling rights for new position */
8917 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8919 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8920 oldEP = (signed char)board[EP_STATUS];
8921 board[EP_STATUS] = EP_NONE;
8923 if( board[toY][toX] != EmptySquare )
8924 board[EP_STATUS] = EP_CAPTURE;
8926 if (fromY == DROP_RANK) {
8928 piece = board[toY][toX] = (ChessSquare) fromX;
8932 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8933 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8934 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8936 if( board[fromY][fromX] == WhitePawn ) {
8937 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8938 board[EP_STATUS] = EP_PAWN_MOVE;
8940 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8941 gameInfo.variant != VariantBerolina || toX < fromX)
8942 board[EP_STATUS] = toX | berolina;
8943 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8944 gameInfo.variant != VariantBerolina || toX > fromX)
8945 board[EP_STATUS] = toX;
8948 if( board[fromY][fromX] == BlackPawn ) {
8949 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8950 board[EP_STATUS] = EP_PAWN_MOVE;
8951 if( toY-fromY== -2) {
8952 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8953 gameInfo.variant != VariantBerolina || toX < fromX)
8954 board[EP_STATUS] = toX | berolina;
8955 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8956 gameInfo.variant != VariantBerolina || toX > fromX)
8957 board[EP_STATUS] = toX;
8961 for(i=0; i<nrCastlingRights; i++) {
8962 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8963 board[CASTLING][i] == toX && castlingRank[i] == toY
8964 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8967 if (fromX == toX && fromY == toY) return;
8969 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8970 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8971 if(gameInfo.variant == VariantKnightmate)
8972 king += (int) WhiteUnicorn - (int) WhiteKing;
8974 /* Code added by Tord: */
8975 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8976 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8977 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8978 board[fromY][fromX] = EmptySquare;
8979 board[toY][toX] = EmptySquare;
8980 if((toX > fromX) != (piece == WhiteRook)) {
8981 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8983 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8985 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8986 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8987 board[fromY][fromX] = EmptySquare;
8988 board[toY][toX] = EmptySquare;
8989 if((toX > fromX) != (piece == BlackRook)) {
8990 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8992 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8994 /* End of code added by Tord */
8996 } else if (board[fromY][fromX] == king
8997 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8998 && toY == fromY && toX > fromX+1) {
8999 board[fromY][fromX] = EmptySquare;
9000 board[toY][toX] = king;
9001 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9002 board[fromY][BOARD_RGHT-1] = EmptySquare;
9003 } else if (board[fromY][fromX] == king
9004 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9005 && toY == fromY && toX < fromX-1) {
9006 board[fromY][fromX] = EmptySquare;
9007 board[toY][toX] = king;
9008 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9009 board[fromY][BOARD_LEFT] = EmptySquare;
9010 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9011 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9012 && toY >= BOARD_HEIGHT-promoRank
9014 /* white pawn promotion */
9015 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9016 if (board[toY][toX] == EmptySquare) {
9017 board[toY][toX] = WhiteQueen;
9019 if(gameInfo.variant==VariantBughouse ||
9020 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9021 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9022 board[fromY][fromX] = EmptySquare;
9023 } else if ((fromY == BOARD_HEIGHT-4)
9025 && gameInfo.variant != VariantXiangqi
9026 && gameInfo.variant != VariantBerolina
9027 && (board[fromY][fromX] == WhitePawn)
9028 && (board[toY][toX] == EmptySquare)) {
9029 board[fromY][fromX] = EmptySquare;
9030 board[toY][toX] = WhitePawn;
9031 captured = board[toY - 1][toX];
9032 board[toY - 1][toX] = EmptySquare;
9033 } else if ((fromY == BOARD_HEIGHT-4)
9035 && gameInfo.variant == VariantBerolina
9036 && (board[fromY][fromX] == WhitePawn)
9037 && (board[toY][toX] == EmptySquare)) {
9038 board[fromY][fromX] = EmptySquare;
9039 board[toY][toX] = WhitePawn;
9040 if(oldEP & EP_BEROLIN_A) {
9041 captured = board[fromY][fromX-1];
9042 board[fromY][fromX-1] = EmptySquare;
9043 }else{ captured = board[fromY][fromX+1];
9044 board[fromY][fromX+1] = EmptySquare;
9046 } else if (board[fromY][fromX] == king
9047 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9048 && toY == fromY && toX > fromX+1) {
9049 board[fromY][fromX] = EmptySquare;
9050 board[toY][toX] = king;
9051 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9052 board[fromY][BOARD_RGHT-1] = EmptySquare;
9053 } else if (board[fromY][fromX] == king
9054 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9055 && toY == fromY && toX < fromX-1) {
9056 board[fromY][fromX] = EmptySquare;
9057 board[toY][toX] = king;
9058 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9059 board[fromY][BOARD_LEFT] = EmptySquare;
9060 } else if (fromY == 7 && fromX == 3
9061 && board[fromY][fromX] == BlackKing
9062 && toY == 7 && toX == 5) {
9063 board[fromY][fromX] = EmptySquare;
9064 board[toY][toX] = BlackKing;
9065 board[fromY][7] = EmptySquare;
9066 board[toY][4] = BlackRook;
9067 } else if (fromY == 7 && fromX == 3
9068 && board[fromY][fromX] == BlackKing
9069 && toY == 7 && toX == 1) {
9070 board[fromY][fromX] = EmptySquare;
9071 board[toY][toX] = BlackKing;
9072 board[fromY][0] = EmptySquare;
9073 board[toY][2] = BlackRook;
9074 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9075 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9078 /* black pawn promotion */
9079 board[toY][toX] = CharToPiece(ToLower(promoChar));
9080 if (board[toY][toX] == EmptySquare) {
9081 board[toY][toX] = BlackQueen;
9083 if(gameInfo.variant==VariantBughouse ||
9084 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9085 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9086 board[fromY][fromX] = EmptySquare;
9087 } else if ((fromY == 3)
9089 && gameInfo.variant != VariantXiangqi
9090 && gameInfo.variant != VariantBerolina
9091 && (board[fromY][fromX] == BlackPawn)
9092 && (board[toY][toX] == EmptySquare)) {
9093 board[fromY][fromX] = EmptySquare;
9094 board[toY][toX] = BlackPawn;
9095 captured = board[toY + 1][toX];
9096 board[toY + 1][toX] = EmptySquare;
9097 } else if ((fromY == 3)
9099 && gameInfo.variant == VariantBerolina
9100 && (board[fromY][fromX] == BlackPawn)
9101 && (board[toY][toX] == EmptySquare)) {
9102 board[fromY][fromX] = EmptySquare;
9103 board[toY][toX] = BlackPawn;
9104 if(oldEP & EP_BEROLIN_A) {
9105 captured = board[fromY][fromX-1];
9106 board[fromY][fromX-1] = EmptySquare;
9107 }else{ captured = board[fromY][fromX+1];
9108 board[fromY][fromX+1] = EmptySquare;
9111 board[toY][toX] = board[fromY][fromX];
9112 board[fromY][fromX] = EmptySquare;
9116 if (gameInfo.holdingsWidth != 0) {
9118 /* !!A lot more code needs to be written to support holdings */
9119 /* [HGM] OK, so I have written it. Holdings are stored in the */
9120 /* penultimate board files, so they are automaticlly stored */
9121 /* in the game history. */
9122 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9123 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9124 /* Delete from holdings, by decreasing count */
9125 /* and erasing image if necessary */
9126 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9127 if(p < (int) BlackPawn) { /* white drop */
9128 p -= (int)WhitePawn;
9129 p = PieceToNumber((ChessSquare)p);
9130 if(p >= gameInfo.holdingsSize) p = 0;
9131 if(--board[p][BOARD_WIDTH-2] <= 0)
9132 board[p][BOARD_WIDTH-1] = EmptySquare;
9133 if((int)board[p][BOARD_WIDTH-2] < 0)
9134 board[p][BOARD_WIDTH-2] = 0;
9135 } else { /* black drop */
9136 p -= (int)BlackPawn;
9137 p = PieceToNumber((ChessSquare)p);
9138 if(p >= gameInfo.holdingsSize) p = 0;
9139 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9140 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9141 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9142 board[BOARD_HEIGHT-1-p][1] = 0;
9145 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9146 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9147 /* [HGM] holdings: Add to holdings, if holdings exist */
9148 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9149 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9150 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9153 if (p >= (int) BlackPawn) {
9154 p -= (int)BlackPawn;
9155 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9156 /* in Shogi restore piece to its original first */
9157 captured = (ChessSquare) (DEMOTED captured);
9160 p = PieceToNumber((ChessSquare)p);
9161 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9162 board[p][BOARD_WIDTH-2]++;
9163 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9165 p -= (int)WhitePawn;
9166 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9167 captured = (ChessSquare) (DEMOTED captured);
9170 p = PieceToNumber((ChessSquare)p);
9171 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9172 board[BOARD_HEIGHT-1-p][1]++;
9173 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9176 } else if (gameInfo.variant == VariantAtomic) {
9177 if (captured != EmptySquare) {
9179 for (y = toY-1; y <= toY+1; y++) {
9180 for (x = toX-1; x <= toX+1; x++) {
9181 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9182 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9183 board[y][x] = EmptySquare;
9187 board[toY][toX] = EmptySquare;
9190 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9191 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9193 if(promoChar == '+') {
9194 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9195 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9196 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9197 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9199 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9200 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9201 // [HGM] superchess: take promotion piece out of holdings
9202 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9203 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9204 if(!--board[k][BOARD_WIDTH-2])
9205 board[k][BOARD_WIDTH-1] = EmptySquare;
9207 if(!--board[BOARD_HEIGHT-1-k][1])
9208 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9214 /* Updates forwardMostMove */
9216 MakeMove(fromX, fromY, toX, toY, promoChar)
9217 int fromX, fromY, toX, toY;
9220 // forwardMostMove++; // [HGM] bare: moved downstream
9222 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9223 int timeLeft; static int lastLoadFlag=0; int king, piece;
9224 piece = boards[forwardMostMove][fromY][fromX];
9225 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9226 if(gameInfo.variant == VariantKnightmate)
9227 king += (int) WhiteUnicorn - (int) WhiteKing;
9228 if(forwardMostMove == 0) {
9230 fprintf(serverMoves, "%s;", second.tidy);
9231 fprintf(serverMoves, "%s;", first.tidy);
9232 if(!blackPlaysFirst)
9233 fprintf(serverMoves, "%s;", second.tidy);
9234 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9235 lastLoadFlag = loadFlag;
9237 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9238 // print castling suffix
9239 if( toY == fromY && piece == king ) {
9241 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9243 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9246 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9247 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9248 boards[forwardMostMove][toY][toX] == EmptySquare
9249 && fromX != toX && fromY != toY)
9250 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9252 if(promoChar != NULLCHAR)
9253 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9255 fprintf(serverMoves, "/%d/%d",
9256 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9257 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9258 else timeLeft = blackTimeRemaining/1000;
9259 fprintf(serverMoves, "/%d", timeLeft);
9261 fflush(serverMoves);
9264 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9265 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9269 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9270 if (commentList[forwardMostMove+1] != NULL) {
9271 free(commentList[forwardMostMove+1]);
9272 commentList[forwardMostMove+1] = NULL;
9274 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9275 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9276 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9277 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9278 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9279 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9280 gameInfo.result = GameUnfinished;
9281 if (gameInfo.resultDetails != NULL) {
9282 free(gameInfo.resultDetails);
9283 gameInfo.resultDetails = NULL;
9285 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9286 moveList[forwardMostMove - 1]);
9287 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9288 PosFlags(forwardMostMove - 1),
9289 fromY, fromX, toY, toX, promoChar,
9290 parseList[forwardMostMove - 1]);
9291 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9297 if(gameInfo.variant != VariantShogi)
9298 strcat(parseList[forwardMostMove - 1], "+");
9302 strcat(parseList[forwardMostMove - 1], "#");
9305 if (appData.debugMode) {
9306 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9311 /* Updates currentMove if not pausing */
9313 ShowMove(fromX, fromY, toX, toY)
9315 int instant = (gameMode == PlayFromGameFile) ?
9316 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9317 if(appData.noGUI) return;
9318 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9320 if (forwardMostMove == currentMove + 1) {
9321 AnimateMove(boards[forwardMostMove - 1],
9322 fromX, fromY, toX, toY);
9324 if (appData.highlightLastMove) {
9325 SetHighlights(fromX, fromY, toX, toY);
9328 currentMove = forwardMostMove;
9331 if (instant) return;
9333 DisplayMove(currentMove - 1);
9334 DrawPosition(FALSE, boards[currentMove]);
9335 DisplayBothClocks();
9336 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9337 DisplayBook(currentMove);
9340 void SendEgtPath(ChessProgramState *cps)
9341 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9342 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9344 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9347 char c, *q = name+1, *r, *s;
9349 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9350 while(*p && *p != ',') *q++ = *p++;
9352 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9353 strcmp(name, ",nalimov:") == 0 ) {
9354 // take nalimov path from the menu-changeable option first, if it is defined
9355 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9356 SendToProgram(buf,cps); // send egtbpath command for nalimov
9358 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9359 (s = StrStr(appData.egtFormats, name)) != NULL) {
9360 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9361 s = r = StrStr(s, ":") + 1; // beginning of path info
9362 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9363 c = *r; *r = 0; // temporarily null-terminate path info
9364 *--q = 0; // strip of trailig ':' from name
9365 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9367 SendToProgram(buf,cps); // send egtbpath command for this format
9369 if(*p == ',') p++; // read away comma to position for next format name
9374 InitChessProgram(cps, setup)
9375 ChessProgramState *cps;
9376 int setup; /* [HGM] needed to setup FRC opening position */
9378 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9379 if (appData.noChessProgram) return;
9380 hintRequested = FALSE;
9381 bookRequested = FALSE;
9383 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9384 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9385 if(cps->memSize) { /* [HGM] memory */
9386 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9387 SendToProgram(buf, cps);
9389 SendEgtPath(cps); /* [HGM] EGT */
9390 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9391 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9392 SendToProgram(buf, cps);
9395 SendToProgram(cps->initString, cps);
9396 if (gameInfo.variant != VariantNormal &&
9397 gameInfo.variant != VariantLoadable
9398 /* [HGM] also send variant if board size non-standard */
9399 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9401 char *v = VariantName(gameInfo.variant);
9402 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9403 /* [HGM] in protocol 1 we have to assume all variants valid */
9404 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9405 DisplayFatalError(buf, 0, 1);
9409 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9410 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9411 if( gameInfo.variant == VariantXiangqi )
9412 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9413 if( gameInfo.variant == VariantShogi )
9414 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9415 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9416 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9417 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9418 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9419 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9420 if( gameInfo.variant == VariantCourier )
9421 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9422 if( gameInfo.variant == VariantSuper )
9423 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9424 if( gameInfo.variant == VariantGreat )
9425 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9426 if( gameInfo.variant == VariantSChess )
9427 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9430 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9431 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9432 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9433 if(StrStr(cps->variants, b) == NULL) {
9434 // specific sized variant not known, check if general sizing allowed
9435 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9436 if(StrStr(cps->variants, "boardsize") == NULL) {
9437 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9438 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9439 DisplayFatalError(buf, 0, 1);
9442 /* [HGM] here we really should compare with the maximum supported board size */
9445 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9446 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9447 SendToProgram(buf, cps);
9449 currentlyInitializedVariant = gameInfo.variant;
9451 /* [HGM] send opening position in FRC to first engine */
9453 SendToProgram("force\n", cps);
9455 /* engine is now in force mode! Set flag to wake it up after first move. */
9456 setboardSpoiledMachineBlack = 1;
9460 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9461 SendToProgram(buf, cps);
9463 cps->maybeThinking = FALSE;
9464 cps->offeredDraw = 0;
9465 if (!appData.icsActive) {
9466 SendTimeControl(cps, movesPerSession, timeControl,
9467 timeIncrement, appData.searchDepth,
9470 if (appData.showThinking
9471 // [HGM] thinking: four options require thinking output to be sent
9472 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9474 SendToProgram("post\n", cps);
9476 SendToProgram("hard\n", cps);
9477 if (!appData.ponderNextMove) {
9478 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9479 it without being sure what state we are in first. "hard"
9480 is not a toggle, so that one is OK.
9482 SendToProgram("easy\n", cps);
9485 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9486 SendToProgram(buf, cps);
9488 cps->initDone = TRUE;
9493 StartChessProgram(cps)
9494 ChessProgramState *cps;
9499 if (appData.noChessProgram) return;
9500 cps->initDone = FALSE;
9502 if (strcmp(cps->host, "localhost") == 0) {
9503 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9504 } else if (*appData.remoteShell == NULLCHAR) {
9505 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9507 if (*appData.remoteUser == NULLCHAR) {
9508 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9511 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9512 cps->host, appData.remoteUser, cps->program);
9514 err = StartChildProcess(buf, "", &cps->pr);
9518 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9519 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9520 if(cps != &first) return;
9521 appData.noChessProgram = TRUE;
9524 // DisplayFatalError(buf, err, 1);
9525 // cps->pr = NoProc;
9530 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9531 if (cps->protocolVersion > 1) {
9532 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9533 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9534 cps->comboCnt = 0; // and values of combo boxes
9535 SendToProgram(buf, cps);
9537 SendToProgram("xboard\n", cps);
9542 TwoMachinesEventIfReady P((void))
9544 static int curMess = 0;
9545 if (first.lastPing != first.lastPong) {
9546 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9547 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9550 if (second.lastPing != second.lastPong) {
9551 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9552 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9555 DisplayMessage("", ""); curMess = 0;
9561 MakeName(char *template)
9565 static char buf[MSG_SIZ];
9569 clock = time((time_t *)NULL);
9570 tm = localtime(&clock);
9572 while(*p++ = *template++) if(p[-1] == '%') {
9573 switch(*template++) {
9574 case 0: *p = 0; return buf;
9575 case 'Y': i = tm->tm_year+1900; break;
9576 case 'y': i = tm->tm_year-100; break;
9577 case 'M': i = tm->tm_mon+1; break;
9578 case 'd': i = tm->tm_mday; break;
9579 case 'h': i = tm->tm_hour; break;
9580 case 'm': i = tm->tm_min; break;
9581 case 's': i = tm->tm_sec; break;
9584 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9590 CountPlayers(char *p)
9593 while(p = strchr(p, '\n')) p++, n++; // count participants
9598 WriteTourneyFile(char *results)
9599 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9600 FILE *f = fopen(appData.tourneyFile, "w");
9601 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9602 // create a file with tournament description
9603 fprintf(f, "-participants {%s}\n", appData.participants);
9604 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9605 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9606 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9607 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9608 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9609 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9610 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9611 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9612 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9613 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9614 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9616 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9618 fprintf(f, "-mps %d\n", appData.movesPerSession);
9619 fprintf(f, "-tc %s\n", appData.timeControl);
9620 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9622 fprintf(f, "-results \"%s\"\n", results);
9628 CreateTourney(char *name)
9631 if(name[0] == NULLCHAR) {
9632 if(appData.participants[0])
9633 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9636 f = fopen(name, "r");
9637 if(f) { // file exists
9638 ASSIGN(appData.tourneyFile, name);
9639 ParseArgsFromFile(f); // parse it
9641 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9642 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9643 DisplayError(_("Not enough participants"), 0);
9646 ASSIGN(appData.tourneyFile, name);
9647 if((f = WriteTourneyFile("")) == NULL) return 0;
9650 appData.noChessProgram = FALSE;
9651 appData.clockMode = TRUE;
9656 #define MAXENGINES 1000
9657 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9659 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9661 char buf[MSG_SIZ], *p, *q;
9665 while(*p && *p != '\n') *q++ = *p++;
9667 if(engineList[i]) free(engineList[i]);
9668 engineList[i] = strdup(buf);
9670 TidyProgramName(engineList[i], "localhost", buf);
9671 if(engineMnemonic[i]) free(engineMnemonic[i]);
9672 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9674 sscanf(q + 8, "%s", buf + strlen(buf));
9677 engineMnemonic[i] = strdup(buf);
9679 if(i > MAXENGINES - 2) break;
9681 engineList[i] = NULL;
9684 // following implemented as macro to avoid type limitations
9685 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9687 void SwapEngines(int n)
9688 { // swap settings for first engine and other engine (so far only some selected options)
9693 SWAP(chessProgram, p)
9695 SWAP(hasOwnBookUCI, h)
9696 SWAP(protocolVersion, h)
9698 SWAP(scoreIsAbsolute, h)
9705 SetPlayer(int player)
9706 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9708 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9709 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9710 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9711 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9713 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9714 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9715 ParseArgsFromString(buf);
9721 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9722 { // determine players from game number
9723 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9725 if(appData.tourneyType == 0) {
9726 roundsPerCycle = (nPlayers - 1) | 1;
9727 pairingsPerRound = nPlayers / 2;
9728 } else if(appData.tourneyType > 0) {
9729 roundsPerCycle = nPlayers - appData.tourneyType;
9730 pairingsPerRound = appData.tourneyType;
9732 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9733 gamesPerCycle = gamesPerRound * roundsPerCycle;
9734 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9735 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9736 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9737 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9738 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9739 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9741 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9742 if(appData.roundSync) *syncInterval = gamesPerRound;
9744 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9746 if(appData.tourneyType == 0) {
9747 if(curPairing == (nPlayers-1)/2 ) {
9748 *whitePlayer = curRound;
9749 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9751 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9752 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9753 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9754 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9756 } else if(appData.tourneyType > 0) {
9757 *whitePlayer = curPairing;
9758 *blackPlayer = curRound + appData.tourneyType;
9761 // take care of white/black alternation per round.
9762 // For cycles and games this is already taken care of by default, derived from matchGame!
9763 return curRound & 1;
9767 NextTourneyGame(int nr, int *swapColors)
9768 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9770 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9772 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9773 tf = fopen(appData.tourneyFile, "r");
9774 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9775 ParseArgsFromFile(tf); fclose(tf);
9776 InitTimeControls(); // TC might be altered from tourney file
9778 nPlayers = CountPlayers(appData.participants); // count participants
9779 if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9780 if(nr>=0 && !pairingReceived) {
9782 if(pairing.pr == NoProc) StartChessProgram(&pairing);
9783 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9784 SendToProgram(buf, &pairing);
9785 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9786 SendToProgram(buf, &pairing);
9787 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9789 pairingReceived = 0; // ... so we continue here
9790 syncInterval = nPlayers/2; *swapColors = 0;
9791 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9792 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9793 matchGame = 1; roundNr = nr / syncInterval + 1;
9795 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9798 p = q = appData.results;
9799 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9800 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9801 DisplayMessage(_("Waiting for other game(s)"),"");
9802 waitingForGame = TRUE;
9803 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9806 waitingForGame = FALSE;
9809 if(first.pr != NoProc) return 1; // engines already loaded
9811 // redefine engines, engine dir, etc.
9812 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9813 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9815 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9816 SwapEngines(1); // and make that valid for second engine by swapping
9817 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9818 InitEngine(&second, 1);
9819 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9825 { // performs game initialization that does not invoke engines, and then tries to start the game
9826 int firstWhite, swapColors = 0;
9827 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9828 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9829 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9830 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9831 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9832 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9833 Reset(FALSE, first.pr != NoProc);
9834 appData.noChessProgram = FALSE;
9835 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9839 void UserAdjudicationEvent( int result )
9841 ChessMove gameResult = GameIsDrawn;
9844 gameResult = WhiteWins;
9846 else if( result < 0 ) {
9847 gameResult = BlackWins;
9850 if( gameMode == TwoMachinesPlay ) {
9851 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9856 // [HGM] save: calculate checksum of game to make games easily identifiable
9857 int StringCheckSum(char *s)
9860 if(s==NULL) return 0;
9861 while(*s) i = i*259 + *s++;
9868 for(i=backwardMostMove; i<forwardMostMove; i++) {
9869 sum += pvInfoList[i].depth;
9870 sum += StringCheckSum(parseList[i]);
9871 sum += StringCheckSum(commentList[i]);
9874 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9875 return sum + StringCheckSum(commentList[i]);
9876 } // end of save patch
9879 GameEnds(result, resultDetails, whosays)
9881 char *resultDetails;
9884 GameMode nextGameMode;
9886 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9888 if(endingGame) return; /* [HGM] crash: forbid recursion */
9890 if(twoBoards) { // [HGM] dual: switch back to one board
9891 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9892 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9894 if (appData.debugMode) {
9895 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9896 result, resultDetails ? resultDetails : "(null)", whosays);
9899 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9901 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9902 /* If we are playing on ICS, the server decides when the
9903 game is over, but the engine can offer to draw, claim
9907 if (appData.zippyPlay && first.initDone) {
9908 if (result == GameIsDrawn) {
9909 /* In case draw still needs to be claimed */
9910 SendToICS(ics_prefix);
9911 SendToICS("draw\n");
9912 } else if (StrCaseStr(resultDetails, "resign")) {
9913 SendToICS(ics_prefix);
9914 SendToICS("resign\n");
9918 endingGame = 0; /* [HGM] crash */
9922 /* If we're loading the game from a file, stop */
9923 if (whosays == GE_FILE) {
9924 (void) StopLoadGameTimer();
9928 /* Cancel draw offers */
9929 first.offeredDraw = second.offeredDraw = 0;
9931 /* If this is an ICS game, only ICS can really say it's done;
9932 if not, anyone can. */
9933 isIcsGame = (gameMode == IcsPlayingWhite ||
9934 gameMode == IcsPlayingBlack ||
9935 gameMode == IcsObserving ||
9936 gameMode == IcsExamining);
9938 if (!isIcsGame || whosays == GE_ICS) {
9939 /* OK -- not an ICS game, or ICS said it was done */
9941 if (!isIcsGame && !appData.noChessProgram)
9942 SetUserThinkingEnables();
9944 /* [HGM] if a machine claims the game end we verify this claim */
9945 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9946 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9948 ChessMove trueResult = (ChessMove) -1;
9950 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9951 first.twoMachinesColor[0] :
9952 second.twoMachinesColor[0] ;
9954 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9955 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9956 /* [HGM] verify: engine mate claims accepted if they were flagged */
9957 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9959 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9960 /* [HGM] verify: engine mate claims accepted if they were flagged */
9961 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9963 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9964 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9967 // now verify win claims, but not in drop games, as we don't understand those yet
9968 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9969 || gameInfo.variant == VariantGreat) &&
9970 (result == WhiteWins && claimer == 'w' ||
9971 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9972 if (appData.debugMode) {
9973 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9974 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9976 if(result != trueResult) {
9977 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9978 result = claimer == 'w' ? BlackWins : WhiteWins;
9979 resultDetails = buf;
9982 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9983 && (forwardMostMove <= backwardMostMove ||
9984 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9985 (claimer=='b')==(forwardMostMove&1))
9987 /* [HGM] verify: draws that were not flagged are false claims */
9988 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9989 result = claimer == 'w' ? BlackWins : WhiteWins;
9990 resultDetails = buf;
9992 /* (Claiming a loss is accepted no questions asked!) */
9994 /* [HGM] bare: don't allow bare King to win */
9995 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9996 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9997 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9998 && result != GameIsDrawn)
9999 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10000 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10001 int p = (signed char)boards[forwardMostMove][i][j] - color;
10002 if(p >= 0 && p <= (int)WhiteKing) k++;
10004 if (appData.debugMode) {
10005 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10006 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10009 result = GameIsDrawn;
10010 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10011 resultDetails = buf;
10017 if(serverMoves != NULL && !loadFlag) { char c = '=';
10018 if(result==WhiteWins) c = '+';
10019 if(result==BlackWins) c = '-';
10020 if(resultDetails != NULL)
10021 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10023 if (resultDetails != NULL) {
10024 gameInfo.result = result;
10025 gameInfo.resultDetails = StrSave(resultDetails);
10027 /* display last move only if game was not loaded from file */
10028 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10029 DisplayMove(currentMove - 1);
10031 if (forwardMostMove != 0) {
10032 if (gameMode != PlayFromGameFile && gameMode != EditGame
10033 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10035 if (*appData.saveGameFile != NULLCHAR) {
10036 SaveGameToFile(appData.saveGameFile, TRUE);
10037 } else if (appData.autoSaveGames) {
10040 if (*appData.savePositionFile != NULLCHAR) {
10041 SavePositionToFile(appData.savePositionFile);
10046 /* Tell program how game ended in case it is learning */
10047 /* [HGM] Moved this to after saving the PGN, just in case */
10048 /* engine died and we got here through time loss. In that */
10049 /* case we will get a fatal error writing the pipe, which */
10050 /* would otherwise lose us the PGN. */
10051 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10052 /* output during GameEnds should never be fatal anymore */
10053 if (gameMode == MachinePlaysWhite ||
10054 gameMode == MachinePlaysBlack ||
10055 gameMode == TwoMachinesPlay ||
10056 gameMode == IcsPlayingWhite ||
10057 gameMode == IcsPlayingBlack ||
10058 gameMode == BeginningOfGame) {
10060 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10062 if (first.pr != NoProc) {
10063 SendToProgram(buf, &first);
10065 if (second.pr != NoProc &&
10066 gameMode == TwoMachinesPlay) {
10067 SendToProgram(buf, &second);
10072 if (appData.icsActive) {
10073 if (appData.quietPlay &&
10074 (gameMode == IcsPlayingWhite ||
10075 gameMode == IcsPlayingBlack)) {
10076 SendToICS(ics_prefix);
10077 SendToICS("set shout 1\n");
10079 nextGameMode = IcsIdle;
10080 ics_user_moved = FALSE;
10081 /* clean up premove. It's ugly when the game has ended and the
10082 * premove highlights are still on the board.
10085 gotPremove = FALSE;
10086 ClearPremoveHighlights();
10087 DrawPosition(FALSE, boards[currentMove]);
10089 if (whosays == GE_ICS) {
10092 if (gameMode == IcsPlayingWhite)
10094 else if(gameMode == IcsPlayingBlack)
10095 PlayIcsLossSound();
10098 if (gameMode == IcsPlayingBlack)
10100 else if(gameMode == IcsPlayingWhite)
10101 PlayIcsLossSound();
10104 PlayIcsDrawSound();
10107 PlayIcsUnfinishedSound();
10110 } else if (gameMode == EditGame ||
10111 gameMode == PlayFromGameFile ||
10112 gameMode == AnalyzeMode ||
10113 gameMode == AnalyzeFile) {
10114 nextGameMode = gameMode;
10116 nextGameMode = EndOfGame;
10121 nextGameMode = gameMode;
10124 if (appData.noChessProgram) {
10125 gameMode = nextGameMode;
10127 endingGame = 0; /* [HGM] crash */
10132 /* Put first chess program into idle state */
10133 if (first.pr != NoProc &&
10134 (gameMode == MachinePlaysWhite ||
10135 gameMode == MachinePlaysBlack ||
10136 gameMode == TwoMachinesPlay ||
10137 gameMode == IcsPlayingWhite ||
10138 gameMode == IcsPlayingBlack ||
10139 gameMode == BeginningOfGame)) {
10140 SendToProgram("force\n", &first);
10141 if (first.usePing) {
10143 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10144 SendToProgram(buf, &first);
10147 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10148 /* Kill off first chess program */
10149 if (first.isr != NULL)
10150 RemoveInputSource(first.isr);
10153 if (first.pr != NoProc) {
10155 DoSleep( appData.delayBeforeQuit );
10156 SendToProgram("quit\n", &first);
10157 DoSleep( appData.delayAfterQuit );
10158 DestroyChildProcess(first.pr, first.useSigterm);
10162 if (second.reuse) {
10163 /* Put second chess program into idle state */
10164 if (second.pr != NoProc &&
10165 gameMode == TwoMachinesPlay) {
10166 SendToProgram("force\n", &second);
10167 if (second.usePing) {
10169 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10170 SendToProgram(buf, &second);
10173 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10174 /* Kill off second chess program */
10175 if (second.isr != NULL)
10176 RemoveInputSource(second.isr);
10179 if (second.pr != NoProc) {
10180 DoSleep( appData.delayBeforeQuit );
10181 SendToProgram("quit\n", &second);
10182 DoSleep( appData.delayAfterQuit );
10183 DestroyChildProcess(second.pr, second.useSigterm);
10185 second.pr = NoProc;
10188 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10189 char resChar = '=';
10193 if (first.twoMachinesColor[0] == 'w') {
10196 second.matchWins++;
10201 if (first.twoMachinesColor[0] == 'b') {
10204 second.matchWins++;
10207 case GameUnfinished:
10213 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10214 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10215 ReserveGame(nextGame, resChar); // sets nextGame
10216 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10217 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10218 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10220 if (nextGame <= appData.matchGames && !abortMatch) {
10221 gameMode = nextGameMode;
10222 matchGame = nextGame; // this will be overruled in tourney mode!
10223 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10224 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10225 endingGame = 0; /* [HGM] crash */
10228 gameMode = nextGameMode;
10229 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10230 first.tidy, second.tidy,
10231 first.matchWins, second.matchWins,
10232 appData.matchGames - (first.matchWins + second.matchWins));
10233 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10234 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10235 first.twoMachinesColor = "black\n";
10236 second.twoMachinesColor = "white\n";
10238 first.twoMachinesColor = "white\n";
10239 second.twoMachinesColor = "black\n";
10243 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10244 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10246 gameMode = nextGameMode;
10248 endingGame = 0; /* [HGM] crash */
10249 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10250 if(matchMode == TRUE) { // match through command line: exit with or without popup
10252 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10254 } else DisplayFatalError(buf, 0, 0);
10255 } else { // match through menu; just stop, with or without popup
10256 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10258 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10259 } else DisplayNote(buf);
10261 if(ranking) free(ranking);
10265 /* Assumes program was just initialized (initString sent).
10266 Leaves program in force mode. */
10268 FeedMovesToProgram(cps, upto)
10269 ChessProgramState *cps;
10274 if (appData.debugMode)
10275 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10276 startedFromSetupPosition ? "position and " : "",
10277 backwardMostMove, upto, cps->which);
10278 if(currentlyInitializedVariant != gameInfo.variant) {
10280 // [HGM] variantswitch: make engine aware of new variant
10281 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10282 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10283 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10284 SendToProgram(buf, cps);
10285 currentlyInitializedVariant = gameInfo.variant;
10287 SendToProgram("force\n", cps);
10288 if (startedFromSetupPosition) {
10289 SendBoard(cps, backwardMostMove);
10290 if (appData.debugMode) {
10291 fprintf(debugFP, "feedMoves\n");
10294 for (i = backwardMostMove; i < upto; i++) {
10295 SendMoveToProgram(i, cps);
10301 ResurrectChessProgram()
10303 /* The chess program may have exited.
10304 If so, restart it and feed it all the moves made so far. */
10305 static int doInit = 0;
10307 if (appData.noChessProgram) return 1;
10309 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10310 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10311 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10312 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10314 if (first.pr != NoProc) return 1;
10315 StartChessProgram(&first);
10317 InitChessProgram(&first, FALSE);
10318 FeedMovesToProgram(&first, currentMove);
10320 if (!first.sendTime) {
10321 /* can't tell gnuchess what its clock should read,
10322 so we bow to its notion. */
10324 timeRemaining[0][currentMove] = whiteTimeRemaining;
10325 timeRemaining[1][currentMove] = blackTimeRemaining;
10328 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10329 appData.icsEngineAnalyze) && first.analysisSupport) {
10330 SendToProgram("analyze\n", &first);
10331 first.analyzing = TRUE;
10337 * Button procedures
10340 Reset(redraw, init)
10345 if (appData.debugMode) {
10346 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10347 redraw, init, gameMode);
10349 CleanupTail(); // [HGM] vari: delete any stored variations
10350 pausing = pauseExamInvalid = FALSE;
10351 startedFromSetupPosition = blackPlaysFirst = FALSE;
10353 whiteFlag = blackFlag = FALSE;
10354 userOfferedDraw = FALSE;
10355 hintRequested = bookRequested = FALSE;
10356 first.maybeThinking = FALSE;
10357 second.maybeThinking = FALSE;
10358 first.bookSuspend = FALSE; // [HGM] book
10359 second.bookSuspend = FALSE;
10360 thinkOutput[0] = NULLCHAR;
10361 lastHint[0] = NULLCHAR;
10362 ClearGameInfo(&gameInfo);
10363 gameInfo.variant = StringToVariant(appData.variant);
10364 ics_user_moved = ics_clock_paused = FALSE;
10365 ics_getting_history = H_FALSE;
10367 white_holding[0] = black_holding[0] = NULLCHAR;
10368 ClearProgramStats();
10369 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10373 flipView = appData.flipView;
10374 ClearPremoveHighlights();
10375 gotPremove = FALSE;
10376 alarmSounded = FALSE;
10378 GameEnds(EndOfFile, NULL, GE_PLAYER);
10379 if(appData.serverMovesName != NULL) {
10380 /* [HGM] prepare to make moves file for broadcasting */
10381 clock_t t = clock();
10382 if(serverMoves != NULL) fclose(serverMoves);
10383 serverMoves = fopen(appData.serverMovesName, "r");
10384 if(serverMoves != NULL) {
10385 fclose(serverMoves);
10386 /* delay 15 sec before overwriting, so all clients can see end */
10387 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10389 serverMoves = fopen(appData.serverMovesName, "w");
10393 gameMode = BeginningOfGame;
10395 if(appData.icsActive) gameInfo.variant = VariantNormal;
10396 currentMove = forwardMostMove = backwardMostMove = 0;
10397 InitPosition(redraw);
10398 for (i = 0; i < MAX_MOVES; i++) {
10399 if (commentList[i] != NULL) {
10400 free(commentList[i]);
10401 commentList[i] = NULL;
10405 timeRemaining[0][0] = whiteTimeRemaining;
10406 timeRemaining[1][0] = blackTimeRemaining;
10408 if (first.pr == NULL) {
10409 StartChessProgram(&first);
10412 InitChessProgram(&first, startedFromSetupPosition);
10415 DisplayMessage("", "");
10416 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10417 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10424 if (!AutoPlayOneMove())
10426 if (matchMode || appData.timeDelay == 0)
10428 if (appData.timeDelay < 0)
10430 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10439 int fromX, fromY, toX, toY;
10441 if (appData.debugMode) {
10442 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10445 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10448 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10449 pvInfoList[currentMove].depth = programStats.depth;
10450 pvInfoList[currentMove].score = programStats.score;
10451 pvInfoList[currentMove].time = 0;
10452 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10455 if (currentMove >= forwardMostMove) {
10456 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10457 gameMode = EditGame;
10460 /* [AS] Clear current move marker at the end of a game */
10461 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10466 toX = moveList[currentMove][2] - AAA;
10467 toY = moveList[currentMove][3] - ONE;
10469 if (moveList[currentMove][1] == '@') {
10470 if (appData.highlightLastMove) {
10471 SetHighlights(-1, -1, toX, toY);
10474 fromX = moveList[currentMove][0] - AAA;
10475 fromY = moveList[currentMove][1] - ONE;
10477 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10479 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10481 if (appData.highlightLastMove) {
10482 SetHighlights(fromX, fromY, toX, toY);
10485 DisplayMove(currentMove);
10486 SendMoveToProgram(currentMove++, &first);
10487 DisplayBothClocks();
10488 DrawPosition(FALSE, boards[currentMove]);
10489 // [HGM] PV info: always display, routine tests if empty
10490 DisplayComment(currentMove - 1, commentList[currentMove]);
10496 LoadGameOneMove(readAhead)
10497 ChessMove readAhead;
10499 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10500 char promoChar = NULLCHAR;
10501 ChessMove moveType;
10502 char move[MSG_SIZ];
10505 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10506 gameMode != AnalyzeMode && gameMode != Training) {
10511 yyboardindex = forwardMostMove;
10512 if (readAhead != EndOfFile) {
10513 moveType = readAhead;
10515 if (gameFileFP == NULL)
10517 moveType = (ChessMove) Myylex();
10521 switch (moveType) {
10523 if (appData.debugMode)
10524 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10527 /* append the comment but don't display it */
10528 AppendComment(currentMove, p, FALSE);
10531 case WhiteCapturesEnPassant:
10532 case BlackCapturesEnPassant:
10533 case WhitePromotion:
10534 case BlackPromotion:
10535 case WhiteNonPromotion:
10536 case BlackNonPromotion:
10538 case WhiteKingSideCastle:
10539 case WhiteQueenSideCastle:
10540 case BlackKingSideCastle:
10541 case BlackQueenSideCastle:
10542 case WhiteKingSideCastleWild:
10543 case WhiteQueenSideCastleWild:
10544 case BlackKingSideCastleWild:
10545 case BlackQueenSideCastleWild:
10547 case WhiteHSideCastleFR:
10548 case WhiteASideCastleFR:
10549 case BlackHSideCastleFR:
10550 case BlackASideCastleFR:
10552 if (appData.debugMode)
10553 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10554 fromX = currentMoveString[0] - AAA;
10555 fromY = currentMoveString[1] - ONE;
10556 toX = currentMoveString[2] - AAA;
10557 toY = currentMoveString[3] - ONE;
10558 promoChar = currentMoveString[4];
10563 if (appData.debugMode)
10564 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10565 fromX = moveType == WhiteDrop ?
10566 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10567 (int) CharToPiece(ToLower(currentMoveString[0]));
10569 toX = currentMoveString[2] - AAA;
10570 toY = currentMoveString[3] - ONE;
10576 case GameUnfinished:
10577 if (appData.debugMode)
10578 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10579 p = strchr(yy_text, '{');
10580 if (p == NULL) p = strchr(yy_text, '(');
10583 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10585 q = strchr(p, *p == '{' ? '}' : ')');
10586 if (q != NULL) *q = NULLCHAR;
10589 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10590 GameEnds(moveType, p, GE_FILE);
10592 if (cmailMsgLoaded) {
10594 flipView = WhiteOnMove(currentMove);
10595 if (moveType == GameUnfinished) flipView = !flipView;
10596 if (appData.debugMode)
10597 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10602 if (appData.debugMode)
10603 fprintf(debugFP, "Parser hit end of file\n");
10604 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10610 if (WhiteOnMove(currentMove)) {
10611 GameEnds(BlackWins, "Black mates", GE_FILE);
10613 GameEnds(WhiteWins, "White mates", GE_FILE);
10617 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10623 case MoveNumberOne:
10624 if (lastLoadGameStart == GNUChessGame) {
10625 /* GNUChessGames have numbers, but they aren't move numbers */
10626 if (appData.debugMode)
10627 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10628 yy_text, (int) moveType);
10629 return LoadGameOneMove(EndOfFile); /* tail recursion */
10631 /* else fall thru */
10636 /* Reached start of next game in file */
10637 if (appData.debugMode)
10638 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10639 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10645 if (WhiteOnMove(currentMove)) {
10646 GameEnds(BlackWins, "Black mates", GE_FILE);
10648 GameEnds(WhiteWins, "White mates", GE_FILE);
10652 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10658 case PositionDiagram: /* should not happen; ignore */
10659 case ElapsedTime: /* ignore */
10660 case NAG: /* ignore */
10661 if (appData.debugMode)
10662 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10663 yy_text, (int) moveType);
10664 return LoadGameOneMove(EndOfFile); /* tail recursion */
10667 if (appData.testLegality) {
10668 if (appData.debugMode)
10669 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10670 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10671 (forwardMostMove / 2) + 1,
10672 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10673 DisplayError(move, 0);
10676 if (appData.debugMode)
10677 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10678 yy_text, currentMoveString);
10679 fromX = currentMoveString[0] - AAA;
10680 fromY = currentMoveString[1] - ONE;
10681 toX = currentMoveString[2] - AAA;
10682 toY = currentMoveString[3] - ONE;
10683 promoChar = currentMoveString[4];
10687 case AmbiguousMove:
10688 if (appData.debugMode)
10689 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10690 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10691 (forwardMostMove / 2) + 1,
10692 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10693 DisplayError(move, 0);
10698 case ImpossibleMove:
10699 if (appData.debugMode)
10700 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10701 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10702 (forwardMostMove / 2) + 1,
10703 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10704 DisplayError(move, 0);
10710 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10711 DrawPosition(FALSE, boards[currentMove]);
10712 DisplayBothClocks();
10713 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10714 DisplayComment(currentMove - 1, commentList[currentMove]);
10716 (void) StopLoadGameTimer();
10718 cmailOldMove = forwardMostMove;
10721 /* currentMoveString is set as a side-effect of yylex */
10723 thinkOutput[0] = NULLCHAR;
10724 MakeMove(fromX, fromY, toX, toY, promoChar);
10725 currentMove = forwardMostMove;
10730 /* Load the nth game from the given file */
10732 LoadGameFromFile(filename, n, title, useList)
10736 /*Boolean*/ int useList;
10741 if (strcmp(filename, "-") == 0) {
10745 f = fopen(filename, "rb");
10747 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10748 DisplayError(buf, errno);
10752 if (fseek(f, 0, 0) == -1) {
10753 /* f is not seekable; probably a pipe */
10756 if (useList && n == 0) {
10757 int error = GameListBuild(f);
10759 DisplayError(_("Cannot build game list"), error);
10760 } else if (!ListEmpty(&gameList) &&
10761 ((ListGame *) gameList.tailPred)->number > 1) {
10762 GameListPopUp(f, title);
10769 return LoadGame(f, n, title, FALSE);
10774 MakeRegisteredMove()
10776 int fromX, fromY, toX, toY;
10778 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10779 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10782 if (appData.debugMode)
10783 fprintf(debugFP, "Restoring %s for game %d\n",
10784 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10786 thinkOutput[0] = NULLCHAR;
10787 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10788 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10789 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10790 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10791 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10792 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10793 MakeMove(fromX, fromY, toX, toY, promoChar);
10794 ShowMove(fromX, fromY, toX, toY);
10796 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10803 if (WhiteOnMove(currentMove)) {
10804 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10806 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10811 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10818 if (WhiteOnMove(currentMove)) {
10819 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10821 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10826 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10837 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10839 CmailLoadGame(f, gameNumber, title, useList)
10847 if (gameNumber > nCmailGames) {
10848 DisplayError(_("No more games in this message"), 0);
10851 if (f == lastLoadGameFP) {
10852 int offset = gameNumber - lastLoadGameNumber;
10854 cmailMsg[0] = NULLCHAR;
10855 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10856 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10857 nCmailMovesRegistered--;
10859 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10860 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10861 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10864 if (! RegisterMove()) return FALSE;
10868 retVal = LoadGame(f, gameNumber, title, useList);
10870 /* Make move registered during previous look at this game, if any */
10871 MakeRegisteredMove();
10873 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10874 commentList[currentMove]
10875 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10876 DisplayComment(currentMove - 1, commentList[currentMove]);
10882 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10887 int gameNumber = lastLoadGameNumber + offset;
10888 if (lastLoadGameFP == NULL) {
10889 DisplayError(_("No game has been loaded yet"), 0);
10892 if (gameNumber <= 0) {
10893 DisplayError(_("Can't back up any further"), 0);
10896 if (cmailMsgLoaded) {
10897 return CmailLoadGame(lastLoadGameFP, gameNumber,
10898 lastLoadGameTitle, lastLoadGameUseList);
10900 return LoadGame(lastLoadGameFP, gameNumber,
10901 lastLoadGameTitle, lastLoadGameUseList);
10907 /* Load the nth game from open file f */
10909 LoadGame(f, gameNumber, title, useList)
10917 int gn = gameNumber;
10918 ListGame *lg = NULL;
10919 int numPGNTags = 0;
10921 GameMode oldGameMode;
10922 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10924 if (appData.debugMode)
10925 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10927 if (gameMode == Training )
10928 SetTrainingModeOff();
10930 oldGameMode = gameMode;
10931 if (gameMode != BeginningOfGame) {
10932 Reset(FALSE, TRUE);
10936 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10937 fclose(lastLoadGameFP);
10941 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10944 fseek(f, lg->offset, 0);
10945 GameListHighlight(gameNumber);
10949 DisplayError(_("Game number out of range"), 0);
10954 if (fseek(f, 0, 0) == -1) {
10955 if (f == lastLoadGameFP ?
10956 gameNumber == lastLoadGameNumber + 1 :
10960 DisplayError(_("Can't seek on game file"), 0);
10965 lastLoadGameFP = f;
10966 lastLoadGameNumber = gameNumber;
10967 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10968 lastLoadGameUseList = useList;
10972 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10973 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10974 lg->gameInfo.black);
10976 } else if (*title != NULLCHAR) {
10977 if (gameNumber > 1) {
10978 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10981 DisplayTitle(title);
10985 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10986 gameMode = PlayFromGameFile;
10990 currentMove = forwardMostMove = backwardMostMove = 0;
10991 CopyBoard(boards[0], initialPosition);
10995 * Skip the first gn-1 games in the file.
10996 * Also skip over anything that precedes an identifiable
10997 * start of game marker, to avoid being confused by
10998 * garbage at the start of the file. Currently
10999 * recognized start of game markers are the move number "1",
11000 * the pattern "gnuchess .* game", the pattern
11001 * "^[#;%] [^ ]* game file", and a PGN tag block.
11002 * A game that starts with one of the latter two patterns
11003 * will also have a move number 1, possibly
11004 * following a position diagram.
11005 * 5-4-02: Let's try being more lenient and allowing a game to
11006 * start with an unnumbered move. Does that break anything?
11008 cm = lastLoadGameStart = EndOfFile;
11010 yyboardindex = forwardMostMove;
11011 cm = (ChessMove) Myylex();
11014 if (cmailMsgLoaded) {
11015 nCmailGames = CMAIL_MAX_GAMES - gn;
11018 DisplayError(_("Game not found in file"), 0);
11025 lastLoadGameStart = cm;
11028 case MoveNumberOne:
11029 switch (lastLoadGameStart) {
11034 case MoveNumberOne:
11036 gn--; /* count this game */
11037 lastLoadGameStart = cm;
11046 switch (lastLoadGameStart) {
11049 case MoveNumberOne:
11051 gn--; /* count this game */
11052 lastLoadGameStart = cm;
11055 lastLoadGameStart = cm; /* game counted already */
11063 yyboardindex = forwardMostMove;
11064 cm = (ChessMove) Myylex();
11065 } while (cm == PGNTag || cm == Comment);
11072 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11073 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11074 != CMAIL_OLD_RESULT) {
11076 cmailResult[ CMAIL_MAX_GAMES
11077 - gn - 1] = CMAIL_OLD_RESULT;
11083 /* Only a NormalMove can be at the start of a game
11084 * without a position diagram. */
11085 if (lastLoadGameStart == EndOfFile ) {
11087 lastLoadGameStart = MoveNumberOne;
11096 if (appData.debugMode)
11097 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11099 if (cm == XBoardGame) {
11100 /* Skip any header junk before position diagram and/or move 1 */
11102 yyboardindex = forwardMostMove;
11103 cm = (ChessMove) Myylex();
11105 if (cm == EndOfFile ||
11106 cm == GNUChessGame || cm == XBoardGame) {
11107 /* Empty game; pretend end-of-file and handle later */
11112 if (cm == MoveNumberOne || cm == PositionDiagram ||
11113 cm == PGNTag || cm == Comment)
11116 } else if (cm == GNUChessGame) {
11117 if (gameInfo.event != NULL) {
11118 free(gameInfo.event);
11120 gameInfo.event = StrSave(yy_text);
11123 startedFromSetupPosition = FALSE;
11124 while (cm == PGNTag) {
11125 if (appData.debugMode)
11126 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11127 err = ParsePGNTag(yy_text, &gameInfo);
11128 if (!err) numPGNTags++;
11130 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11131 if(gameInfo.variant != oldVariant) {
11132 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11133 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11134 InitPosition(TRUE);
11135 oldVariant = gameInfo.variant;
11136 if (appData.debugMode)
11137 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11141 if (gameInfo.fen != NULL) {
11142 Board initial_position;
11143 startedFromSetupPosition = TRUE;
11144 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11146 DisplayError(_("Bad FEN position in file"), 0);
11149 CopyBoard(boards[0], initial_position);
11150 if (blackPlaysFirst) {
11151 currentMove = forwardMostMove = backwardMostMove = 1;
11152 CopyBoard(boards[1], initial_position);
11153 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11154 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11155 timeRemaining[0][1] = whiteTimeRemaining;
11156 timeRemaining[1][1] = blackTimeRemaining;
11157 if (commentList[0] != NULL) {
11158 commentList[1] = commentList[0];
11159 commentList[0] = NULL;
11162 currentMove = forwardMostMove = backwardMostMove = 0;
11164 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11166 initialRulePlies = FENrulePlies;
11167 for( i=0; i< nrCastlingRights; i++ )
11168 initialRights[i] = initial_position[CASTLING][i];
11170 yyboardindex = forwardMostMove;
11171 free(gameInfo.fen);
11172 gameInfo.fen = NULL;
11175 yyboardindex = forwardMostMove;
11176 cm = (ChessMove) Myylex();
11178 /* Handle comments interspersed among the tags */
11179 while (cm == Comment) {
11181 if (appData.debugMode)
11182 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11184 AppendComment(currentMove, p, FALSE);
11185 yyboardindex = forwardMostMove;
11186 cm = (ChessMove) Myylex();
11190 /* don't rely on existence of Event tag since if game was
11191 * pasted from clipboard the Event tag may not exist
11193 if (numPGNTags > 0){
11195 if (gameInfo.variant == VariantNormal) {
11196 VariantClass v = StringToVariant(gameInfo.event);
11197 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11198 if(v < VariantShogi) gameInfo.variant = v;
11201 if( appData.autoDisplayTags ) {
11202 tags = PGNTags(&gameInfo);
11203 TagsPopUp(tags, CmailMsg());
11208 /* Make something up, but don't display it now */
11213 if (cm == PositionDiagram) {
11216 Board initial_position;
11218 if (appData.debugMode)
11219 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11221 if (!startedFromSetupPosition) {
11223 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11224 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11235 initial_position[i][j++] = CharToPiece(*p);
11238 while (*p == ' ' || *p == '\t' ||
11239 *p == '\n' || *p == '\r') p++;
11241 if (strncmp(p, "black", strlen("black"))==0)
11242 blackPlaysFirst = TRUE;
11244 blackPlaysFirst = FALSE;
11245 startedFromSetupPosition = TRUE;
11247 CopyBoard(boards[0], initial_position);
11248 if (blackPlaysFirst) {
11249 currentMove = forwardMostMove = backwardMostMove = 1;
11250 CopyBoard(boards[1], initial_position);
11251 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11252 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11253 timeRemaining[0][1] = whiteTimeRemaining;
11254 timeRemaining[1][1] = blackTimeRemaining;
11255 if (commentList[0] != NULL) {
11256 commentList[1] = commentList[0];
11257 commentList[0] = NULL;
11260 currentMove = forwardMostMove = backwardMostMove = 0;
11263 yyboardindex = forwardMostMove;
11264 cm = (ChessMove) Myylex();
11267 if (first.pr == NoProc) {
11268 StartChessProgram(&first);
11270 InitChessProgram(&first, FALSE);
11271 SendToProgram("force\n", &first);
11272 if (startedFromSetupPosition) {
11273 SendBoard(&first, forwardMostMove);
11274 if (appData.debugMode) {
11275 fprintf(debugFP, "Load Game\n");
11277 DisplayBothClocks();
11280 /* [HGM] server: flag to write setup moves in broadcast file as one */
11281 loadFlag = appData.suppressLoadMoves;
11283 while (cm == Comment) {
11285 if (appData.debugMode)
11286 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11288 AppendComment(currentMove, p, FALSE);
11289 yyboardindex = forwardMostMove;
11290 cm = (ChessMove) Myylex();
11293 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11294 cm == WhiteWins || cm == BlackWins ||
11295 cm == GameIsDrawn || cm == GameUnfinished) {
11296 DisplayMessage("", _("No moves in game"));
11297 if (cmailMsgLoaded) {
11298 if (appData.debugMode)
11299 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11303 DrawPosition(FALSE, boards[currentMove]);
11304 DisplayBothClocks();
11305 gameMode = EditGame;
11312 // [HGM] PV info: routine tests if comment empty
11313 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11314 DisplayComment(currentMove - 1, commentList[currentMove]);
11316 if (!matchMode && appData.timeDelay != 0)
11317 DrawPosition(FALSE, boards[currentMove]);
11319 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11320 programStats.ok_to_send = 1;
11323 /* if the first token after the PGN tags is a move
11324 * and not move number 1, retrieve it from the parser
11326 if (cm != MoveNumberOne)
11327 LoadGameOneMove(cm);
11329 /* load the remaining moves from the file */
11330 while (LoadGameOneMove(EndOfFile)) {
11331 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11332 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11335 /* rewind to the start of the game */
11336 currentMove = backwardMostMove;
11338 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11340 if (oldGameMode == AnalyzeFile ||
11341 oldGameMode == AnalyzeMode) {
11342 AnalyzeFileEvent();
11345 if (matchMode || appData.timeDelay == 0) {
11347 gameMode = EditGame;
11349 } else if (appData.timeDelay > 0) {
11350 AutoPlayGameLoop();
11353 if (appData.debugMode)
11354 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11356 loadFlag = 0; /* [HGM] true game starts */
11360 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11362 ReloadPosition(offset)
11365 int positionNumber = lastLoadPositionNumber + offset;
11366 if (lastLoadPositionFP == NULL) {
11367 DisplayError(_("No position has been loaded yet"), 0);
11370 if (positionNumber <= 0) {
11371 DisplayError(_("Can't back up any further"), 0);
11374 return LoadPosition(lastLoadPositionFP, positionNumber,
11375 lastLoadPositionTitle);
11378 /* Load the nth position from the given file */
11380 LoadPositionFromFile(filename, n, title)
11388 if (strcmp(filename, "-") == 0) {
11389 return LoadPosition(stdin, n, "stdin");
11391 f = fopen(filename, "rb");
11393 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11394 DisplayError(buf, errno);
11397 return LoadPosition(f, n, title);
11402 /* Load the nth position from the given open file, and close it */
11404 LoadPosition(f, positionNumber, title)
11406 int positionNumber;
11409 char *p, line[MSG_SIZ];
11410 Board initial_position;
11411 int i, j, fenMode, pn;
11413 if (gameMode == Training )
11414 SetTrainingModeOff();
11416 if (gameMode != BeginningOfGame) {
11417 Reset(FALSE, TRUE);
11419 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11420 fclose(lastLoadPositionFP);
11422 if (positionNumber == 0) positionNumber = 1;
11423 lastLoadPositionFP = f;
11424 lastLoadPositionNumber = positionNumber;
11425 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11426 if (first.pr == NoProc) {
11427 StartChessProgram(&first);
11428 InitChessProgram(&first, FALSE);
11430 pn = positionNumber;
11431 if (positionNumber < 0) {
11432 /* Negative position number means to seek to that byte offset */
11433 if (fseek(f, -positionNumber, 0) == -1) {
11434 DisplayError(_("Can't seek on position file"), 0);
11439 if (fseek(f, 0, 0) == -1) {
11440 if (f == lastLoadPositionFP ?
11441 positionNumber == lastLoadPositionNumber + 1 :
11442 positionNumber == 1) {
11445 DisplayError(_("Can't seek on position file"), 0);
11450 /* See if this file is FEN or old-style xboard */
11451 if (fgets(line, MSG_SIZ, f) == NULL) {
11452 DisplayError(_("Position not found in file"), 0);
11455 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11456 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11459 if (fenMode || line[0] == '#') pn--;
11461 /* skip positions before number pn */
11462 if (fgets(line, MSG_SIZ, f) == NULL) {
11464 DisplayError(_("Position not found in file"), 0);
11467 if (fenMode || line[0] == '#') pn--;
11472 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11473 DisplayError(_("Bad FEN position in file"), 0);
11477 (void) fgets(line, MSG_SIZ, f);
11478 (void) fgets(line, MSG_SIZ, f);
11480 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11481 (void) fgets(line, MSG_SIZ, f);
11482 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11485 initial_position[i][j++] = CharToPiece(*p);
11489 blackPlaysFirst = FALSE;
11491 (void) fgets(line, MSG_SIZ, f);
11492 if (strncmp(line, "black", strlen("black"))==0)
11493 blackPlaysFirst = TRUE;
11496 startedFromSetupPosition = TRUE;
11498 SendToProgram("force\n", &first);
11499 CopyBoard(boards[0], initial_position);
11500 if (blackPlaysFirst) {
11501 currentMove = forwardMostMove = backwardMostMove = 1;
11502 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11503 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11504 CopyBoard(boards[1], initial_position);
11505 DisplayMessage("", _("Black to play"));
11507 currentMove = forwardMostMove = backwardMostMove = 0;
11508 DisplayMessage("", _("White to play"));
11510 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11511 SendBoard(&first, forwardMostMove);
11512 if (appData.debugMode) {
11514 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11515 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11516 fprintf(debugFP, "Load Position\n");
11519 if (positionNumber > 1) {
11520 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11521 DisplayTitle(line);
11523 DisplayTitle(title);
11525 gameMode = EditGame;
11528 timeRemaining[0][1] = whiteTimeRemaining;
11529 timeRemaining[1][1] = blackTimeRemaining;
11530 DrawPosition(FALSE, boards[currentMove]);
11537 CopyPlayerNameIntoFileName(dest, src)
11540 while (*src != NULLCHAR && *src != ',') {
11545 *(*dest)++ = *src++;
11550 char *DefaultFileName(ext)
11553 static char def[MSG_SIZ];
11556 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11558 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11560 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11562 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11569 /* Save the current game to the given file */
11571 SaveGameToFile(filename, append)
11579 if (strcmp(filename, "-") == 0) {
11580 return SaveGame(stdout, 0, NULL);
11582 f = fopen(filename, append ? "a" : "w");
11584 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11585 DisplayError(buf, errno);
11588 safeStrCpy(buf, lastMsg, MSG_SIZ);
11589 DisplayMessage(_("Waiting for access to save file"), "");
11590 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11591 DisplayMessage(_("Saving game"), "");
11592 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11593 result = SaveGame(f, 0, NULL);
11594 DisplayMessage(buf, "");
11604 static char buf[MSG_SIZ];
11607 p = strchr(str, ' ');
11608 if (p == NULL) return str;
11609 strncpy(buf, str, p - str);
11610 buf[p - str] = NULLCHAR;
11614 #define PGN_MAX_LINE 75
11616 #define PGN_SIDE_WHITE 0
11617 #define PGN_SIDE_BLACK 1
11620 static int FindFirstMoveOutOfBook( int side )
11624 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11625 int index = backwardMostMove;
11626 int has_book_hit = 0;
11628 if( (index % 2) != side ) {
11632 while( index < forwardMostMove ) {
11633 /* Check to see if engine is in book */
11634 int depth = pvInfoList[index].depth;
11635 int score = pvInfoList[index].score;
11641 else if( score == 0 && depth == 63 ) {
11642 in_book = 1; /* Zappa */
11644 else if( score == 2 && depth == 99 ) {
11645 in_book = 1; /* Abrok */
11648 has_book_hit += in_book;
11664 void GetOutOfBookInfo( char * buf )
11668 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11670 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11671 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11675 if( oob[0] >= 0 || oob[1] >= 0 ) {
11676 for( i=0; i<2; i++ ) {
11680 if( i > 0 && oob[0] >= 0 ) {
11681 strcat( buf, " " );
11684 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11685 sprintf( buf+strlen(buf), "%s%.2f",
11686 pvInfoList[idx].score >= 0 ? "+" : "",
11687 pvInfoList[idx].score / 100.0 );
11693 /* Save game in PGN style and close the file */
11698 int i, offset, linelen, newblock;
11702 int movelen, numlen, blank;
11703 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11705 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11707 tm = time((time_t *) NULL);
11709 PrintPGNTags(f, &gameInfo);
11711 if (backwardMostMove > 0 || startedFromSetupPosition) {
11712 char *fen = PositionToFEN(backwardMostMove, NULL);
11713 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11714 fprintf(f, "\n{--------------\n");
11715 PrintPosition(f, backwardMostMove);
11716 fprintf(f, "--------------}\n");
11720 /* [AS] Out of book annotation */
11721 if( appData.saveOutOfBookInfo ) {
11724 GetOutOfBookInfo( buf );
11726 if( buf[0] != '\0' ) {
11727 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11734 i = backwardMostMove;
11738 while (i < forwardMostMove) {
11739 /* Print comments preceding this move */
11740 if (commentList[i] != NULL) {
11741 if (linelen > 0) fprintf(f, "\n");
11742 fprintf(f, "%s", commentList[i]);
11747 /* Format move number */
11749 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11752 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11754 numtext[0] = NULLCHAR;
11756 numlen = strlen(numtext);
11759 /* Print move number */
11760 blank = linelen > 0 && numlen > 0;
11761 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11770 fprintf(f, "%s", numtext);
11774 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11775 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11778 blank = linelen > 0 && movelen > 0;
11779 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11788 fprintf(f, "%s", move_buffer);
11789 linelen += movelen;
11791 /* [AS] Add PV info if present */
11792 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11793 /* [HGM] add time */
11794 char buf[MSG_SIZ]; int seconds;
11796 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11802 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11805 seconds = (seconds + 4)/10; // round to full seconds
11807 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11809 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11812 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11813 pvInfoList[i].score >= 0 ? "+" : "",
11814 pvInfoList[i].score / 100.0,
11815 pvInfoList[i].depth,
11818 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11820 /* Print score/depth */
11821 blank = linelen > 0 && movelen > 0;
11822 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11831 fprintf(f, "%s", move_buffer);
11832 linelen += movelen;
11838 /* Start a new line */
11839 if (linelen > 0) fprintf(f, "\n");
11841 /* Print comments after last move */
11842 if (commentList[i] != NULL) {
11843 fprintf(f, "%s\n", commentList[i]);
11847 if (gameInfo.resultDetails != NULL &&
11848 gameInfo.resultDetails[0] != NULLCHAR) {
11849 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11850 PGNResult(gameInfo.result));
11852 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11856 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11860 /* Save game in old style and close the file */
11862 SaveGameOldStyle(f)
11868 tm = time((time_t *) NULL);
11870 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11873 if (backwardMostMove > 0 || startedFromSetupPosition) {
11874 fprintf(f, "\n[--------------\n");
11875 PrintPosition(f, backwardMostMove);
11876 fprintf(f, "--------------]\n");
11881 i = backwardMostMove;
11882 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11884 while (i < forwardMostMove) {
11885 if (commentList[i] != NULL) {
11886 fprintf(f, "[%s]\n", commentList[i]);
11889 if ((i % 2) == 1) {
11890 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11893 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11895 if (commentList[i] != NULL) {
11899 if (i >= forwardMostMove) {
11903 fprintf(f, "%s\n", parseList[i]);
11908 if (commentList[i] != NULL) {
11909 fprintf(f, "[%s]\n", commentList[i]);
11912 /* This isn't really the old style, but it's close enough */
11913 if (gameInfo.resultDetails != NULL &&
11914 gameInfo.resultDetails[0] != NULLCHAR) {
11915 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11916 gameInfo.resultDetails);
11918 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11925 /* Save the current game to open file f and close the file */
11927 SaveGame(f, dummy, dummy2)
11932 if (gameMode == EditPosition) EditPositionDone(TRUE);
11933 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11934 if (appData.oldSaveStyle)
11935 return SaveGameOldStyle(f);
11937 return SaveGamePGN(f);
11940 /* Save the current position to the given file */
11942 SavePositionToFile(filename)
11948 if (strcmp(filename, "-") == 0) {
11949 return SavePosition(stdout, 0, NULL);
11951 f = fopen(filename, "a");
11953 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11954 DisplayError(buf, errno);
11957 safeStrCpy(buf, lastMsg, MSG_SIZ);
11958 DisplayMessage(_("Waiting for access to save file"), "");
11959 flock(fileno(f), LOCK_EX); // [HGM] lock
11960 DisplayMessage(_("Saving position"), "");
11961 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11962 SavePosition(f, 0, NULL);
11963 DisplayMessage(buf, "");
11969 /* Save the current position to the given open file and close the file */
11971 SavePosition(f, dummy, dummy2)
11979 if (gameMode == EditPosition) EditPositionDone(TRUE);
11980 if (appData.oldSaveStyle) {
11981 tm = time((time_t *) NULL);
11983 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11985 fprintf(f, "[--------------\n");
11986 PrintPosition(f, currentMove);
11987 fprintf(f, "--------------]\n");
11989 fen = PositionToFEN(currentMove, NULL);
11990 fprintf(f, "%s\n", fen);
11998 ReloadCmailMsgEvent(unregister)
12002 static char *inFilename = NULL;
12003 static char *outFilename;
12005 struct stat inbuf, outbuf;
12008 /* Any registered moves are unregistered if unregister is set, */
12009 /* i.e. invoked by the signal handler */
12011 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12012 cmailMoveRegistered[i] = FALSE;
12013 if (cmailCommentList[i] != NULL) {
12014 free(cmailCommentList[i]);
12015 cmailCommentList[i] = NULL;
12018 nCmailMovesRegistered = 0;
12021 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12022 cmailResult[i] = CMAIL_NOT_RESULT;
12026 if (inFilename == NULL) {
12027 /* Because the filenames are static they only get malloced once */
12028 /* and they never get freed */
12029 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12030 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12032 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12033 sprintf(outFilename, "%s.out", appData.cmailGameName);
12036 status = stat(outFilename, &outbuf);
12038 cmailMailedMove = FALSE;
12040 status = stat(inFilename, &inbuf);
12041 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12044 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12045 counts the games, notes how each one terminated, etc.
12047 It would be nice to remove this kludge and instead gather all
12048 the information while building the game list. (And to keep it
12049 in the game list nodes instead of having a bunch of fixed-size
12050 parallel arrays.) Note this will require getting each game's
12051 termination from the PGN tags, as the game list builder does
12052 not process the game moves. --mann
12054 cmailMsgLoaded = TRUE;
12055 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12057 /* Load first game in the file or popup game menu */
12058 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12060 #endif /* !WIN32 */
12068 char string[MSG_SIZ];
12070 if ( cmailMailedMove
12071 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12072 return TRUE; /* Allow free viewing */
12075 /* Unregister move to ensure that we don't leave RegisterMove */
12076 /* with the move registered when the conditions for registering no */
12078 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12079 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12080 nCmailMovesRegistered --;
12082 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12084 free(cmailCommentList[lastLoadGameNumber - 1]);
12085 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12089 if (cmailOldMove == -1) {
12090 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12094 if (currentMove > cmailOldMove + 1) {
12095 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12099 if (currentMove < cmailOldMove) {
12100 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12104 if (forwardMostMove > currentMove) {
12105 /* Silently truncate extra moves */
12109 if ( (currentMove == cmailOldMove + 1)
12110 || ( (currentMove == cmailOldMove)
12111 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12112 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12113 if (gameInfo.result != GameUnfinished) {
12114 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12117 if (commentList[currentMove] != NULL) {
12118 cmailCommentList[lastLoadGameNumber - 1]
12119 = StrSave(commentList[currentMove]);
12121 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12123 if (appData.debugMode)
12124 fprintf(debugFP, "Saving %s for game %d\n",
12125 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12127 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12129 f = fopen(string, "w");
12130 if (appData.oldSaveStyle) {
12131 SaveGameOldStyle(f); /* also closes the file */
12133 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12134 f = fopen(string, "w");
12135 SavePosition(f, 0, NULL); /* also closes the file */
12137 fprintf(f, "{--------------\n");
12138 PrintPosition(f, currentMove);
12139 fprintf(f, "--------------}\n\n");
12141 SaveGame(f, 0, NULL); /* also closes the file*/
12144 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12145 nCmailMovesRegistered ++;
12146 } else if (nCmailGames == 1) {
12147 DisplayError(_("You have not made a move yet"), 0);
12158 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12159 FILE *commandOutput;
12160 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12161 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12167 if (! cmailMsgLoaded) {
12168 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12172 if (nCmailGames == nCmailResults) {
12173 DisplayError(_("No unfinished games"), 0);
12177 #if CMAIL_PROHIBIT_REMAIL
12178 if (cmailMailedMove) {
12179 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);
12180 DisplayError(msg, 0);
12185 if (! (cmailMailedMove || RegisterMove())) return;
12187 if ( cmailMailedMove
12188 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12189 snprintf(string, MSG_SIZ, partCommandString,
12190 appData.debugMode ? " -v" : "", appData.cmailGameName);
12191 commandOutput = popen(string, "r");
12193 if (commandOutput == NULL) {
12194 DisplayError(_("Failed to invoke cmail"), 0);
12196 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12197 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12199 if (nBuffers > 1) {
12200 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12201 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12202 nBytes = MSG_SIZ - 1;
12204 (void) memcpy(msg, buffer, nBytes);
12206 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12208 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12209 cmailMailedMove = TRUE; /* Prevent >1 moves */
12212 for (i = 0; i < nCmailGames; i ++) {
12213 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12218 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12220 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12222 appData.cmailGameName,
12224 LoadGameFromFile(buffer, 1, buffer, FALSE);
12225 cmailMsgLoaded = FALSE;
12229 DisplayInformation(msg);
12230 pclose(commandOutput);
12233 if ((*cmailMsg) != '\0') {
12234 DisplayInformation(cmailMsg);
12239 #endif /* !WIN32 */
12248 int prependComma = 0;
12250 char string[MSG_SIZ]; /* Space for game-list */
12253 if (!cmailMsgLoaded) return "";
12255 if (cmailMailedMove) {
12256 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12258 /* Create a list of games left */
12259 snprintf(string, MSG_SIZ, "[");
12260 for (i = 0; i < nCmailGames; i ++) {
12261 if (! ( cmailMoveRegistered[i]
12262 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12263 if (prependComma) {
12264 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12266 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12270 strcat(string, number);
12273 strcat(string, "]");
12275 if (nCmailMovesRegistered + nCmailResults == 0) {
12276 switch (nCmailGames) {
12278 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12282 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12286 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12291 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12293 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12298 if (nCmailResults == nCmailGames) {
12299 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12301 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12306 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12318 if (gameMode == Training)
12319 SetTrainingModeOff();
12322 cmailMsgLoaded = FALSE;
12323 if (appData.icsActive) {
12324 SendToICS(ics_prefix);
12325 SendToICS("refresh\n");
12335 /* Give up on clean exit */
12339 /* Keep trying for clean exit */
12343 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12345 if (telnetISR != NULL) {
12346 RemoveInputSource(telnetISR);
12348 if (icsPR != NoProc) {
12349 DestroyChildProcess(icsPR, TRUE);
12352 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12353 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12355 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12356 /* make sure this other one finishes before killing it! */
12357 if(endingGame) { int count = 0;
12358 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12359 while(endingGame && count++ < 10) DoSleep(1);
12360 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12363 /* Kill off chess programs */
12364 if (first.pr != NoProc) {
12367 DoSleep( appData.delayBeforeQuit );
12368 SendToProgram("quit\n", &first);
12369 DoSleep( appData.delayAfterQuit );
12370 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12372 if (second.pr != NoProc) {
12373 DoSleep( appData.delayBeforeQuit );
12374 SendToProgram("quit\n", &second);
12375 DoSleep( appData.delayAfterQuit );
12376 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12378 if (first.isr != NULL) {
12379 RemoveInputSource(first.isr);
12381 if (second.isr != NULL) {
12382 RemoveInputSource(second.isr);
12385 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12386 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12388 ShutDownFrontEnd();
12395 if (appData.debugMode)
12396 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12400 if (gameMode == MachinePlaysWhite ||
12401 gameMode == MachinePlaysBlack) {
12404 DisplayBothClocks();
12406 if (gameMode == PlayFromGameFile) {
12407 if (appData.timeDelay >= 0)
12408 AutoPlayGameLoop();
12409 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12410 Reset(FALSE, TRUE);
12411 SendToICS(ics_prefix);
12412 SendToICS("refresh\n");
12413 } else if (currentMove < forwardMostMove) {
12414 ForwardInner(forwardMostMove);
12416 pauseExamInvalid = FALSE;
12418 switch (gameMode) {
12422 pauseExamForwardMostMove = forwardMostMove;
12423 pauseExamInvalid = FALSE;
12426 case IcsPlayingWhite:
12427 case IcsPlayingBlack:
12431 case PlayFromGameFile:
12432 (void) StopLoadGameTimer();
12436 case BeginningOfGame:
12437 if (appData.icsActive) return;
12438 /* else fall through */
12439 case MachinePlaysWhite:
12440 case MachinePlaysBlack:
12441 case TwoMachinesPlay:
12442 if (forwardMostMove == 0)
12443 return; /* don't pause if no one has moved */
12444 if ((gameMode == MachinePlaysWhite &&
12445 !WhiteOnMove(forwardMostMove)) ||
12446 (gameMode == MachinePlaysBlack &&
12447 WhiteOnMove(forwardMostMove))) {
12460 char title[MSG_SIZ];
12462 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12463 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12465 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12466 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12467 parseList[currentMove - 1]);
12470 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12477 char *tags = PGNTags(&gameInfo);
12479 EditTagsPopUp(tags, NULL);
12486 if (appData.noChessProgram || gameMode == AnalyzeMode)
12489 if (gameMode != AnalyzeFile) {
12490 if (!appData.icsEngineAnalyze) {
12492 if (gameMode != EditGame) return;
12494 ResurrectChessProgram();
12495 SendToProgram("analyze\n", &first);
12496 first.analyzing = TRUE;
12497 /*first.maybeThinking = TRUE;*/
12498 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12499 EngineOutputPopUp();
12501 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12506 StartAnalysisClock();
12507 GetTimeMark(&lastNodeCountTime);
12514 if (appData.noChessProgram || gameMode == AnalyzeFile)
12517 if (gameMode != AnalyzeMode) {
12519 if (gameMode != EditGame) return;
12520 ResurrectChessProgram();
12521 SendToProgram("analyze\n", &first);
12522 first.analyzing = TRUE;
12523 /*first.maybeThinking = TRUE;*/
12524 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12525 EngineOutputPopUp();
12527 gameMode = AnalyzeFile;
12532 StartAnalysisClock();
12533 GetTimeMark(&lastNodeCountTime);
12538 MachineWhiteEvent()
12541 char *bookHit = NULL;
12543 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12547 if (gameMode == PlayFromGameFile ||
12548 gameMode == TwoMachinesPlay ||
12549 gameMode == Training ||
12550 gameMode == AnalyzeMode ||
12551 gameMode == EndOfGame)
12554 if (gameMode == EditPosition)
12555 EditPositionDone(TRUE);
12557 if (!WhiteOnMove(currentMove)) {
12558 DisplayError(_("It is not White's turn"), 0);
12562 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12565 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12566 gameMode == AnalyzeFile)
12569 ResurrectChessProgram(); /* in case it isn't running */
12570 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12571 gameMode = MachinePlaysWhite;
12574 gameMode = MachinePlaysWhite;
12578 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12580 if (first.sendName) {
12581 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12582 SendToProgram(buf, &first);
12584 if (first.sendTime) {
12585 if (first.useColors) {
12586 SendToProgram("black\n", &first); /*gnu kludge*/
12588 SendTimeRemaining(&first, TRUE);
12590 if (first.useColors) {
12591 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12593 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12594 SetMachineThinkingEnables();
12595 first.maybeThinking = TRUE;
12599 if (appData.autoFlipView && !flipView) {
12600 flipView = !flipView;
12601 DrawPosition(FALSE, NULL);
12602 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12605 if(bookHit) { // [HGM] book: simulate book reply
12606 static char bookMove[MSG_SIZ]; // a bit generous?
12608 programStats.nodes = programStats.depth = programStats.time =
12609 programStats.score = programStats.got_only_move = 0;
12610 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12612 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12613 strcat(bookMove, bookHit);
12614 HandleMachineMove(bookMove, &first);
12619 MachineBlackEvent()
12622 char *bookHit = NULL;
12624 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12628 if (gameMode == PlayFromGameFile ||
12629 gameMode == TwoMachinesPlay ||
12630 gameMode == Training ||
12631 gameMode == AnalyzeMode ||
12632 gameMode == EndOfGame)
12635 if (gameMode == EditPosition)
12636 EditPositionDone(TRUE);
12638 if (WhiteOnMove(currentMove)) {
12639 DisplayError(_("It is not Black's turn"), 0);
12643 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12646 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12647 gameMode == AnalyzeFile)
12650 ResurrectChessProgram(); /* in case it isn't running */
12651 gameMode = MachinePlaysBlack;
12655 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12657 if (first.sendName) {
12658 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12659 SendToProgram(buf, &first);
12661 if (first.sendTime) {
12662 if (first.useColors) {
12663 SendToProgram("white\n", &first); /*gnu kludge*/
12665 SendTimeRemaining(&first, FALSE);
12667 if (first.useColors) {
12668 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12670 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12671 SetMachineThinkingEnables();
12672 first.maybeThinking = TRUE;
12675 if (appData.autoFlipView && flipView) {
12676 flipView = !flipView;
12677 DrawPosition(FALSE, NULL);
12678 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12680 if(bookHit) { // [HGM] book: simulate book reply
12681 static char bookMove[MSG_SIZ]; // a bit generous?
12683 programStats.nodes = programStats.depth = programStats.time =
12684 programStats.score = programStats.got_only_move = 0;
12685 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12687 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12688 strcat(bookMove, bookHit);
12689 HandleMachineMove(bookMove, &first);
12695 DisplayTwoMachinesTitle()
12698 if (appData.matchGames > 0) {
12699 if(appData.tourneyFile[0]) {
12700 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12701 gameInfo.white, gameInfo.black,
12702 nextGame+1, appData.matchGames+1,
12703 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12705 if (first.twoMachinesColor[0] == 'w') {
12706 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12707 gameInfo.white, gameInfo.black,
12708 first.matchWins, second.matchWins,
12709 matchGame - 1 - (first.matchWins + second.matchWins));
12711 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12712 gameInfo.white, gameInfo.black,
12713 second.matchWins, first.matchWins,
12714 matchGame - 1 - (first.matchWins + second.matchWins));
12717 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12723 SettingsMenuIfReady()
12725 if (second.lastPing != second.lastPong) {
12726 DisplayMessage("", _("Waiting for second chess program"));
12727 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12731 DisplayMessage("", "");
12732 SettingsPopUp(&second);
12736 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12739 if (cps->pr == NULL) {
12740 StartChessProgram(cps);
12741 if (cps->protocolVersion == 1) {
12744 /* kludge: allow timeout for initial "feature" command */
12746 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12747 DisplayMessage("", buf);
12748 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12756 TwoMachinesEvent P((void))
12760 ChessProgramState *onmove;
12761 char *bookHit = NULL;
12762 static int stalling = 0;
12766 if (appData.noChessProgram) return;
12768 switch (gameMode) {
12769 case TwoMachinesPlay:
12771 case MachinePlaysWhite:
12772 case MachinePlaysBlack:
12773 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12774 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12778 case BeginningOfGame:
12779 case PlayFromGameFile:
12782 if (gameMode != EditGame) return;
12785 EditPositionDone(TRUE);
12796 // forwardMostMove = currentMove;
12797 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12799 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12801 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12802 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12803 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12807 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12808 SendToProgram("force\n", &second);
12810 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12813 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12814 if(appData.matchPause>10000 || appData.matchPause<10)
12815 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12816 wait = SubtractTimeMarks(&now, &pauseStart);
12817 if(wait < appData.matchPause) {
12818 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12822 DisplayMessage("", "");
12823 if (startedFromSetupPosition) {
12824 SendBoard(&second, backwardMostMove);
12825 if (appData.debugMode) {
12826 fprintf(debugFP, "Two Machines\n");
12829 for (i = backwardMostMove; i < forwardMostMove; i++) {
12830 SendMoveToProgram(i, &second);
12833 gameMode = TwoMachinesPlay;
12837 DisplayTwoMachinesTitle();
12839 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12844 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12845 SendToProgram(first.computerString, &first);
12846 if (first.sendName) {
12847 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12848 SendToProgram(buf, &first);
12850 SendToProgram(second.computerString, &second);
12851 if (second.sendName) {
12852 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12853 SendToProgram(buf, &second);
12857 if (!first.sendTime || !second.sendTime) {
12858 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12859 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12861 if (onmove->sendTime) {
12862 if (onmove->useColors) {
12863 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12865 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12867 if (onmove->useColors) {
12868 SendToProgram(onmove->twoMachinesColor, onmove);
12870 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12871 // SendToProgram("go\n", onmove);
12872 onmove->maybeThinking = TRUE;
12873 SetMachineThinkingEnables();
12877 if(bookHit) { // [HGM] book: simulate book reply
12878 static char bookMove[MSG_SIZ]; // a bit generous?
12880 programStats.nodes = programStats.depth = programStats.time =
12881 programStats.score = programStats.got_only_move = 0;
12882 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12884 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12885 strcat(bookMove, bookHit);
12886 savedMessage = bookMove; // args for deferred call
12887 savedState = onmove;
12888 ScheduleDelayedEvent(DeferredBookMove, 1);
12895 if (gameMode == Training) {
12896 SetTrainingModeOff();
12897 gameMode = PlayFromGameFile;
12898 DisplayMessage("", _("Training mode off"));
12900 gameMode = Training;
12901 animateTraining = appData.animate;
12903 /* make sure we are not already at the end of the game */
12904 if (currentMove < forwardMostMove) {
12905 SetTrainingModeOn();
12906 DisplayMessage("", _("Training mode on"));
12908 gameMode = PlayFromGameFile;
12909 DisplayError(_("Already at end of game"), 0);
12918 if (!appData.icsActive) return;
12919 switch (gameMode) {
12920 case IcsPlayingWhite:
12921 case IcsPlayingBlack:
12924 case BeginningOfGame:
12932 EditPositionDone(TRUE);
12945 gameMode = IcsIdle;
12956 switch (gameMode) {
12958 SetTrainingModeOff();
12960 case MachinePlaysWhite:
12961 case MachinePlaysBlack:
12962 case BeginningOfGame:
12963 SendToProgram("force\n", &first);
12964 SetUserThinkingEnables();
12966 case PlayFromGameFile:
12967 (void) StopLoadGameTimer();
12968 if (gameFileFP != NULL) {
12973 EditPositionDone(TRUE);
12978 SendToProgram("force\n", &first);
12980 case TwoMachinesPlay:
12981 GameEnds(EndOfFile, NULL, GE_PLAYER);
12982 ResurrectChessProgram();
12983 SetUserThinkingEnables();
12986 ResurrectChessProgram();
12988 case IcsPlayingBlack:
12989 case IcsPlayingWhite:
12990 DisplayError(_("Warning: You are still playing a game"), 0);
12993 DisplayError(_("Warning: You are still observing a game"), 0);
12996 DisplayError(_("Warning: You are still examining a game"), 0);
13007 first.offeredDraw = second.offeredDraw = 0;
13009 if (gameMode == PlayFromGameFile) {
13010 whiteTimeRemaining = timeRemaining[0][currentMove];
13011 blackTimeRemaining = timeRemaining[1][currentMove];
13015 if (gameMode == MachinePlaysWhite ||
13016 gameMode == MachinePlaysBlack ||
13017 gameMode == TwoMachinesPlay ||
13018 gameMode == EndOfGame) {
13019 i = forwardMostMove;
13020 while (i > currentMove) {
13021 SendToProgram("undo\n", &first);
13024 whiteTimeRemaining = timeRemaining[0][currentMove];
13025 blackTimeRemaining = timeRemaining[1][currentMove];
13026 DisplayBothClocks();
13027 if (whiteFlag || blackFlag) {
13028 whiteFlag = blackFlag = 0;
13033 gameMode = EditGame;
13040 EditPositionEvent()
13042 if (gameMode == EditPosition) {
13048 if (gameMode != EditGame) return;
13050 gameMode = EditPosition;
13053 if (currentMove > 0)
13054 CopyBoard(boards[0], boards[currentMove]);
13056 blackPlaysFirst = !WhiteOnMove(currentMove);
13058 currentMove = forwardMostMove = backwardMostMove = 0;
13059 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13066 /* [DM] icsEngineAnalyze - possible call from other functions */
13067 if (appData.icsEngineAnalyze) {
13068 appData.icsEngineAnalyze = FALSE;
13070 DisplayMessage("",_("Close ICS engine analyze..."));
13072 if (first.analysisSupport && first.analyzing) {
13073 SendToProgram("exit\n", &first);
13074 first.analyzing = FALSE;
13076 thinkOutput[0] = NULLCHAR;
13080 EditPositionDone(Boolean fakeRights)
13082 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13084 startedFromSetupPosition = TRUE;
13085 InitChessProgram(&first, FALSE);
13086 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13087 boards[0][EP_STATUS] = EP_NONE;
13088 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13089 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13090 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13091 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13092 } else boards[0][CASTLING][2] = NoRights;
13093 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13094 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13095 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13096 } else boards[0][CASTLING][5] = NoRights;
13098 SendToProgram("force\n", &first);
13099 if (blackPlaysFirst) {
13100 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13101 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13102 currentMove = forwardMostMove = backwardMostMove = 1;
13103 CopyBoard(boards[1], boards[0]);
13105 currentMove = forwardMostMove = backwardMostMove = 0;
13107 SendBoard(&first, forwardMostMove);
13108 if (appData.debugMode) {
13109 fprintf(debugFP, "EditPosDone\n");
13112 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13113 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13114 gameMode = EditGame;
13116 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13117 ClearHighlights(); /* [AS] */
13120 /* Pause for `ms' milliseconds */
13121 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13131 } while (SubtractTimeMarks(&m2, &m1) < ms);
13134 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13136 SendMultiLineToICS(buf)
13139 char temp[MSG_SIZ+1], *p;
13146 strncpy(temp, buf, len);
13151 if (*p == '\n' || *p == '\r')
13156 strcat(temp, "\n");
13158 SendToPlayer(temp, strlen(temp));
13162 SetWhiteToPlayEvent()
13164 if (gameMode == EditPosition) {
13165 blackPlaysFirst = FALSE;
13166 DisplayBothClocks(); /* works because currentMove is 0 */
13167 } else if (gameMode == IcsExamining) {
13168 SendToICS(ics_prefix);
13169 SendToICS("tomove white\n");
13174 SetBlackToPlayEvent()
13176 if (gameMode == EditPosition) {
13177 blackPlaysFirst = TRUE;
13178 currentMove = 1; /* kludge */
13179 DisplayBothClocks();
13181 } else if (gameMode == IcsExamining) {
13182 SendToICS(ics_prefix);
13183 SendToICS("tomove black\n");
13188 EditPositionMenuEvent(selection, x, y)
13189 ChessSquare selection;
13193 ChessSquare piece = boards[0][y][x];
13195 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13197 switch (selection) {
13199 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13200 SendToICS(ics_prefix);
13201 SendToICS("bsetup clear\n");
13202 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13203 SendToICS(ics_prefix);
13204 SendToICS("clearboard\n");
13206 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13207 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13208 for (y = 0; y < BOARD_HEIGHT; y++) {
13209 if (gameMode == IcsExamining) {
13210 if (boards[currentMove][y][x] != EmptySquare) {
13211 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13216 boards[0][y][x] = p;
13221 if (gameMode == EditPosition) {
13222 DrawPosition(FALSE, boards[0]);
13227 SetWhiteToPlayEvent();
13231 SetBlackToPlayEvent();
13235 if (gameMode == IcsExamining) {
13236 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13237 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13240 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13241 if(x == BOARD_LEFT-2) {
13242 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13243 boards[0][y][1] = 0;
13245 if(x == BOARD_RGHT+1) {
13246 if(y >= gameInfo.holdingsSize) break;
13247 boards[0][y][BOARD_WIDTH-2] = 0;
13250 boards[0][y][x] = EmptySquare;
13251 DrawPosition(FALSE, boards[0]);
13256 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13257 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13258 selection = (ChessSquare) (PROMOTED piece);
13259 } else if(piece == EmptySquare) selection = WhiteSilver;
13260 else selection = (ChessSquare)((int)piece - 1);
13264 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13265 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13266 selection = (ChessSquare) (DEMOTED piece);
13267 } else if(piece == EmptySquare) selection = BlackSilver;
13268 else selection = (ChessSquare)((int)piece + 1);
13273 if(gameInfo.variant == VariantShatranj ||
13274 gameInfo.variant == VariantXiangqi ||
13275 gameInfo.variant == VariantCourier ||
13276 gameInfo.variant == VariantMakruk )
13277 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13282 if(gameInfo.variant == VariantXiangqi)
13283 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13284 if(gameInfo.variant == VariantKnightmate)
13285 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13288 if (gameMode == IcsExamining) {
13289 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13290 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13291 PieceToChar(selection), AAA + x, ONE + y);
13294 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13296 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13297 n = PieceToNumber(selection - BlackPawn);
13298 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13299 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13300 boards[0][BOARD_HEIGHT-1-n][1]++;
13302 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13303 n = PieceToNumber(selection);
13304 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13305 boards[0][n][BOARD_WIDTH-1] = selection;
13306 boards[0][n][BOARD_WIDTH-2]++;
13309 boards[0][y][x] = selection;
13310 DrawPosition(TRUE, boards[0]);
13318 DropMenuEvent(selection, x, y)
13319 ChessSquare selection;
13322 ChessMove moveType;
13324 switch (gameMode) {
13325 case IcsPlayingWhite:
13326 case MachinePlaysBlack:
13327 if (!WhiteOnMove(currentMove)) {
13328 DisplayMoveError(_("It is Black's turn"));
13331 moveType = WhiteDrop;
13333 case IcsPlayingBlack:
13334 case MachinePlaysWhite:
13335 if (WhiteOnMove(currentMove)) {
13336 DisplayMoveError(_("It is White's turn"));
13339 moveType = BlackDrop;
13342 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13348 if (moveType == BlackDrop && selection < BlackPawn) {
13349 selection = (ChessSquare) ((int) selection
13350 + (int) BlackPawn - (int) WhitePawn);
13352 if (boards[currentMove][y][x] != EmptySquare) {
13353 DisplayMoveError(_("That square is occupied"));
13357 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13363 /* Accept a pending offer of any kind from opponent */
13365 if (appData.icsActive) {
13366 SendToICS(ics_prefix);
13367 SendToICS("accept\n");
13368 } else if (cmailMsgLoaded) {
13369 if (currentMove == cmailOldMove &&
13370 commentList[cmailOldMove] != NULL &&
13371 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13372 "Black offers a draw" : "White offers a draw")) {
13374 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13375 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13377 DisplayError(_("There is no pending offer on this move"), 0);
13378 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13381 /* Not used for offers from chess program */
13388 /* Decline a pending offer of any kind from opponent */
13390 if (appData.icsActive) {
13391 SendToICS(ics_prefix);
13392 SendToICS("decline\n");
13393 } else if (cmailMsgLoaded) {
13394 if (currentMove == cmailOldMove &&
13395 commentList[cmailOldMove] != NULL &&
13396 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13397 "Black offers a draw" : "White offers a draw")) {
13399 AppendComment(cmailOldMove, "Draw declined", TRUE);
13400 DisplayComment(cmailOldMove - 1, "Draw declined");
13403 DisplayError(_("There is no pending offer on this move"), 0);
13406 /* Not used for offers from chess program */
13413 /* Issue ICS rematch command */
13414 if (appData.icsActive) {
13415 SendToICS(ics_prefix);
13416 SendToICS("rematch\n");
13423 /* Call your opponent's flag (claim a win on time) */
13424 if (appData.icsActive) {
13425 SendToICS(ics_prefix);
13426 SendToICS("flag\n");
13428 switch (gameMode) {
13431 case MachinePlaysWhite:
13434 GameEnds(GameIsDrawn, "Both players ran out of time",
13437 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13439 DisplayError(_("Your opponent is not out of time"), 0);
13442 case MachinePlaysBlack:
13445 GameEnds(GameIsDrawn, "Both players ran out of time",
13448 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13450 DisplayError(_("Your opponent is not out of time"), 0);
13458 ClockClick(int which)
13459 { // [HGM] code moved to back-end from winboard.c
13460 if(which) { // black clock
13461 if (gameMode == EditPosition || gameMode == IcsExamining) {
13462 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13463 SetBlackToPlayEvent();
13464 } else if (gameMode == EditGame || shiftKey) {
13465 AdjustClock(which, -1);
13466 } else if (gameMode == IcsPlayingWhite ||
13467 gameMode == MachinePlaysBlack) {
13470 } else { // white clock
13471 if (gameMode == EditPosition || gameMode == IcsExamining) {
13472 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13473 SetWhiteToPlayEvent();
13474 } else if (gameMode == EditGame || shiftKey) {
13475 AdjustClock(which, -1);
13476 } else if (gameMode == IcsPlayingBlack ||
13477 gameMode == MachinePlaysWhite) {
13486 /* Offer draw or accept pending draw offer from opponent */
13488 if (appData.icsActive) {
13489 /* Note: tournament rules require draw offers to be
13490 made after you make your move but before you punch
13491 your clock. Currently ICS doesn't let you do that;
13492 instead, you immediately punch your clock after making
13493 a move, but you can offer a draw at any time. */
13495 SendToICS(ics_prefix);
13496 SendToICS("draw\n");
13497 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13498 } else if (cmailMsgLoaded) {
13499 if (currentMove == cmailOldMove &&
13500 commentList[cmailOldMove] != NULL &&
13501 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13502 "Black offers a draw" : "White offers a draw")) {
13503 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13504 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13505 } else if (currentMove == cmailOldMove + 1) {
13506 char *offer = WhiteOnMove(cmailOldMove) ?
13507 "White offers a draw" : "Black offers a draw";
13508 AppendComment(currentMove, offer, TRUE);
13509 DisplayComment(currentMove - 1, offer);
13510 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13512 DisplayError(_("You must make your move before offering a draw"), 0);
13513 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13515 } else if (first.offeredDraw) {
13516 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13518 if (first.sendDrawOffers) {
13519 SendToProgram("draw\n", &first);
13520 userOfferedDraw = TRUE;
13528 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13530 if (appData.icsActive) {
13531 SendToICS(ics_prefix);
13532 SendToICS("adjourn\n");
13534 /* Currently GNU Chess doesn't offer or accept Adjourns */
13542 /* Offer Abort or accept pending Abort offer from opponent */
13544 if (appData.icsActive) {
13545 SendToICS(ics_prefix);
13546 SendToICS("abort\n");
13548 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13555 /* Resign. You can do this even if it's not your turn. */
13557 if (appData.icsActive) {
13558 SendToICS(ics_prefix);
13559 SendToICS("resign\n");
13561 switch (gameMode) {
13562 case MachinePlaysWhite:
13563 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13565 case MachinePlaysBlack:
13566 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13569 if (cmailMsgLoaded) {
13571 if (WhiteOnMove(cmailOldMove)) {
13572 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13574 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13576 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13587 StopObservingEvent()
13589 /* Stop observing current games */
13590 SendToICS(ics_prefix);
13591 SendToICS("unobserve\n");
13595 StopExaminingEvent()
13597 /* Stop observing current game */
13598 SendToICS(ics_prefix);
13599 SendToICS("unexamine\n");
13603 ForwardInner(target)
13608 if (appData.debugMode)
13609 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13610 target, currentMove, forwardMostMove);
13612 if (gameMode == EditPosition)
13615 if (gameMode == PlayFromGameFile && !pausing)
13618 if (gameMode == IcsExamining && pausing)
13619 limit = pauseExamForwardMostMove;
13621 limit = forwardMostMove;
13623 if (target > limit) target = limit;
13625 if (target > 0 && moveList[target - 1][0]) {
13626 int fromX, fromY, toX, toY;
13627 toX = moveList[target - 1][2] - AAA;
13628 toY = moveList[target - 1][3] - ONE;
13629 if (moveList[target - 1][1] == '@') {
13630 if (appData.highlightLastMove) {
13631 SetHighlights(-1, -1, toX, toY);
13634 fromX = moveList[target - 1][0] - AAA;
13635 fromY = moveList[target - 1][1] - ONE;
13636 if (target == currentMove + 1) {
13637 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13639 if (appData.highlightLastMove) {
13640 SetHighlights(fromX, fromY, toX, toY);
13644 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13645 gameMode == Training || gameMode == PlayFromGameFile ||
13646 gameMode == AnalyzeFile) {
13647 while (currentMove < target) {
13648 SendMoveToProgram(currentMove++, &first);
13651 currentMove = target;
13654 if (gameMode == EditGame || gameMode == EndOfGame) {
13655 whiteTimeRemaining = timeRemaining[0][currentMove];
13656 blackTimeRemaining = timeRemaining[1][currentMove];
13658 DisplayBothClocks();
13659 DisplayMove(currentMove - 1);
13660 DrawPosition(FALSE, boards[currentMove]);
13661 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13662 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13663 DisplayComment(currentMove - 1, commentList[currentMove]);
13665 DisplayBook(currentMove);
13672 if (gameMode == IcsExamining && !pausing) {
13673 SendToICS(ics_prefix);
13674 SendToICS("forward\n");
13676 ForwardInner(currentMove + 1);
13683 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13684 /* to optimze, we temporarily turn off analysis mode while we feed
13685 * the remaining moves to the engine. Otherwise we get analysis output
13688 if (first.analysisSupport) {
13689 SendToProgram("exit\nforce\n", &first);
13690 first.analyzing = FALSE;
13694 if (gameMode == IcsExamining && !pausing) {
13695 SendToICS(ics_prefix);
13696 SendToICS("forward 999999\n");
13698 ForwardInner(forwardMostMove);
13701 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13702 /* we have fed all the moves, so reactivate analysis mode */
13703 SendToProgram("analyze\n", &first);
13704 first.analyzing = TRUE;
13705 /*first.maybeThinking = TRUE;*/
13706 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13711 BackwardInner(target)
13714 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13716 if (appData.debugMode)
13717 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13718 target, currentMove, forwardMostMove);
13720 if (gameMode == EditPosition) return;
13721 if (currentMove <= backwardMostMove) {
13723 DrawPosition(full_redraw, boards[currentMove]);
13726 if (gameMode == PlayFromGameFile && !pausing)
13729 if (moveList[target][0]) {
13730 int fromX, fromY, toX, toY;
13731 toX = moveList[target][2] - AAA;
13732 toY = moveList[target][3] - ONE;
13733 if (moveList[target][1] == '@') {
13734 if (appData.highlightLastMove) {
13735 SetHighlights(-1, -1, toX, toY);
13738 fromX = moveList[target][0] - AAA;
13739 fromY = moveList[target][1] - ONE;
13740 if (target == currentMove - 1) {
13741 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13743 if (appData.highlightLastMove) {
13744 SetHighlights(fromX, fromY, toX, toY);
13748 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13749 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13750 while (currentMove > target) {
13751 SendToProgram("undo\n", &first);
13755 currentMove = target;
13758 if (gameMode == EditGame || gameMode == EndOfGame) {
13759 whiteTimeRemaining = timeRemaining[0][currentMove];
13760 blackTimeRemaining = timeRemaining[1][currentMove];
13762 DisplayBothClocks();
13763 DisplayMove(currentMove - 1);
13764 DrawPosition(full_redraw, boards[currentMove]);
13765 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13766 // [HGM] PV info: routine tests if comment empty
13767 DisplayComment(currentMove - 1, commentList[currentMove]);
13768 DisplayBook(currentMove);
13774 if (gameMode == IcsExamining && !pausing) {
13775 SendToICS(ics_prefix);
13776 SendToICS("backward\n");
13778 BackwardInner(currentMove - 1);
13785 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13786 /* to optimize, we temporarily turn off analysis mode while we undo
13787 * all the moves. Otherwise we get analysis output after each undo.
13789 if (first.analysisSupport) {
13790 SendToProgram("exit\nforce\n", &first);
13791 first.analyzing = FALSE;
13795 if (gameMode == IcsExamining && !pausing) {
13796 SendToICS(ics_prefix);
13797 SendToICS("backward 999999\n");
13799 BackwardInner(backwardMostMove);
13802 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13803 /* we have fed all the moves, so reactivate analysis mode */
13804 SendToProgram("analyze\n", &first);
13805 first.analyzing = TRUE;
13806 /*first.maybeThinking = TRUE;*/
13807 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13814 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13815 if (to >= forwardMostMove) to = forwardMostMove;
13816 if (to <= backwardMostMove) to = backwardMostMove;
13817 if (to < currentMove) {
13825 RevertEvent(Boolean annotate)
13827 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13830 if (gameMode != IcsExamining) {
13831 DisplayError(_("You are not examining a game"), 0);
13835 DisplayError(_("You can't revert while pausing"), 0);
13838 SendToICS(ics_prefix);
13839 SendToICS("revert\n");
13845 switch (gameMode) {
13846 case MachinePlaysWhite:
13847 case MachinePlaysBlack:
13848 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13849 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13852 if (forwardMostMove < 2) return;
13853 currentMove = forwardMostMove = forwardMostMove - 2;
13854 whiteTimeRemaining = timeRemaining[0][currentMove];
13855 blackTimeRemaining = timeRemaining[1][currentMove];
13856 DisplayBothClocks();
13857 DisplayMove(currentMove - 1);
13858 ClearHighlights();/*!! could figure this out*/
13859 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13860 SendToProgram("remove\n", &first);
13861 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13864 case BeginningOfGame:
13868 case IcsPlayingWhite:
13869 case IcsPlayingBlack:
13870 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13871 SendToICS(ics_prefix);
13872 SendToICS("takeback 2\n");
13874 SendToICS(ics_prefix);
13875 SendToICS("takeback 1\n");
13884 ChessProgramState *cps;
13886 switch (gameMode) {
13887 case MachinePlaysWhite:
13888 if (!WhiteOnMove(forwardMostMove)) {
13889 DisplayError(_("It is your turn"), 0);
13894 case MachinePlaysBlack:
13895 if (WhiteOnMove(forwardMostMove)) {
13896 DisplayError(_("It is your turn"), 0);
13901 case TwoMachinesPlay:
13902 if (WhiteOnMove(forwardMostMove) ==
13903 (first.twoMachinesColor[0] == 'w')) {
13909 case BeginningOfGame:
13913 SendToProgram("?\n", cps);
13917 TruncateGameEvent()
13920 if (gameMode != EditGame) return;
13927 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13928 if (forwardMostMove > currentMove) {
13929 if (gameInfo.resultDetails != NULL) {
13930 free(gameInfo.resultDetails);
13931 gameInfo.resultDetails = NULL;
13932 gameInfo.result = GameUnfinished;
13934 forwardMostMove = currentMove;
13935 HistorySet(parseList, backwardMostMove, forwardMostMove,
13943 if (appData.noChessProgram) return;
13944 switch (gameMode) {
13945 case MachinePlaysWhite:
13946 if (WhiteOnMove(forwardMostMove)) {
13947 DisplayError(_("Wait until your turn"), 0);
13951 case BeginningOfGame:
13952 case MachinePlaysBlack:
13953 if (!WhiteOnMove(forwardMostMove)) {
13954 DisplayError(_("Wait until your turn"), 0);
13959 DisplayError(_("No hint available"), 0);
13962 SendToProgram("hint\n", &first);
13963 hintRequested = TRUE;
13969 if (appData.noChessProgram) return;
13970 switch (gameMode) {
13971 case MachinePlaysWhite:
13972 if (WhiteOnMove(forwardMostMove)) {
13973 DisplayError(_("Wait until your turn"), 0);
13977 case BeginningOfGame:
13978 case MachinePlaysBlack:
13979 if (!WhiteOnMove(forwardMostMove)) {
13980 DisplayError(_("Wait until your turn"), 0);
13985 EditPositionDone(TRUE);
13987 case TwoMachinesPlay:
13992 SendToProgram("bk\n", &first);
13993 bookOutput[0] = NULLCHAR;
13994 bookRequested = TRUE;
14000 char *tags = PGNTags(&gameInfo);
14001 TagsPopUp(tags, CmailMsg());
14005 /* end button procedures */
14008 PrintPosition(fp, move)
14014 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14015 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14016 char c = PieceToChar(boards[move][i][j]);
14017 fputc(c == 'x' ? '.' : c, fp);
14018 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14021 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14022 fprintf(fp, "white to play\n");
14024 fprintf(fp, "black to play\n");
14031 if (gameInfo.white != NULL) {
14032 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14038 /* Find last component of program's own name, using some heuristics */
14040 TidyProgramName(prog, host, buf)
14041 char *prog, *host, buf[MSG_SIZ];
14044 int local = (strcmp(host, "localhost") == 0);
14045 while (!local && (p = strchr(prog, ';')) != NULL) {
14047 while (*p == ' ') p++;
14050 if (*prog == '"' || *prog == '\'') {
14051 q = strchr(prog + 1, *prog);
14053 q = strchr(prog, ' ');
14055 if (q == NULL) q = prog + strlen(prog);
14057 while (p >= prog && *p != '/' && *p != '\\') p--;
14059 if(p == prog && *p == '"') p++;
14060 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14061 memcpy(buf, p, q - p);
14062 buf[q - p] = NULLCHAR;
14070 TimeControlTagValue()
14073 if (!appData.clockMode) {
14074 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14075 } else if (movesPerSession > 0) {
14076 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14077 } else if (timeIncrement == 0) {
14078 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14080 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14082 return StrSave(buf);
14088 /* This routine is used only for certain modes */
14089 VariantClass v = gameInfo.variant;
14090 ChessMove r = GameUnfinished;
14093 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14094 r = gameInfo.result;
14095 p = gameInfo.resultDetails;
14096 gameInfo.resultDetails = NULL;
14098 ClearGameInfo(&gameInfo);
14099 gameInfo.variant = v;
14101 switch (gameMode) {
14102 case MachinePlaysWhite:
14103 gameInfo.event = StrSave( appData.pgnEventHeader );
14104 gameInfo.site = StrSave(HostName());
14105 gameInfo.date = PGNDate();
14106 gameInfo.round = StrSave("-");
14107 gameInfo.white = StrSave(first.tidy);
14108 gameInfo.black = StrSave(UserName());
14109 gameInfo.timeControl = TimeControlTagValue();
14112 case MachinePlaysBlack:
14113 gameInfo.event = StrSave( appData.pgnEventHeader );
14114 gameInfo.site = StrSave(HostName());
14115 gameInfo.date = PGNDate();
14116 gameInfo.round = StrSave("-");
14117 gameInfo.white = StrSave(UserName());
14118 gameInfo.black = StrSave(first.tidy);
14119 gameInfo.timeControl = TimeControlTagValue();
14122 case TwoMachinesPlay:
14123 gameInfo.event = StrSave( appData.pgnEventHeader );
14124 gameInfo.site = StrSave(HostName());
14125 gameInfo.date = PGNDate();
14128 snprintf(buf, MSG_SIZ, "%d", roundNr);
14129 gameInfo.round = StrSave(buf);
14131 gameInfo.round = StrSave("-");
14133 if (first.twoMachinesColor[0] == 'w') {
14134 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14135 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14137 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14138 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14140 gameInfo.timeControl = TimeControlTagValue();
14144 gameInfo.event = StrSave("Edited game");
14145 gameInfo.site = StrSave(HostName());
14146 gameInfo.date = PGNDate();
14147 gameInfo.round = StrSave("-");
14148 gameInfo.white = StrSave("-");
14149 gameInfo.black = StrSave("-");
14150 gameInfo.result = r;
14151 gameInfo.resultDetails = p;
14155 gameInfo.event = StrSave("Edited position");
14156 gameInfo.site = StrSave(HostName());
14157 gameInfo.date = PGNDate();
14158 gameInfo.round = StrSave("-");
14159 gameInfo.white = StrSave("-");
14160 gameInfo.black = StrSave("-");
14163 case IcsPlayingWhite:
14164 case IcsPlayingBlack:
14169 case PlayFromGameFile:
14170 gameInfo.event = StrSave("Game from non-PGN file");
14171 gameInfo.site = StrSave(HostName());
14172 gameInfo.date = PGNDate();
14173 gameInfo.round = StrSave("-");
14174 gameInfo.white = StrSave("?");
14175 gameInfo.black = StrSave("?");
14184 ReplaceComment(index, text)
14192 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14193 pvInfoList[index-1].depth == len &&
14194 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14195 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14196 while (*text == '\n') text++;
14197 len = strlen(text);
14198 while (len > 0 && text[len - 1] == '\n') len--;
14200 if (commentList[index] != NULL)
14201 free(commentList[index]);
14204 commentList[index] = NULL;
14207 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14208 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14209 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14210 commentList[index] = (char *) malloc(len + 2);
14211 strncpy(commentList[index], text, len);
14212 commentList[index][len] = '\n';
14213 commentList[index][len + 1] = NULLCHAR;
14215 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14217 commentList[index] = (char *) malloc(len + 7);
14218 safeStrCpy(commentList[index], "{\n", 3);
14219 safeStrCpy(commentList[index]+2, text, len+1);
14220 commentList[index][len+2] = NULLCHAR;
14221 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14222 strcat(commentList[index], "\n}\n");
14236 if (ch == '\r') continue;
14238 } while (ch != '\0');
14242 AppendComment(index, text, addBraces)
14245 Boolean addBraces; // [HGM] braces: tells if we should add {}
14250 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14251 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14254 while (*text == '\n') text++;
14255 len = strlen(text);
14256 while (len > 0 && text[len - 1] == '\n') len--;
14258 if (len == 0) return;
14260 if (commentList[index] != NULL) {
14261 old = commentList[index];
14262 oldlen = strlen(old);
14263 while(commentList[index][oldlen-1] == '\n')
14264 commentList[index][--oldlen] = NULLCHAR;
14265 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14266 safeStrCpy(commentList[index], old, oldlen + len + 6);
14268 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14269 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14270 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14271 while (*text == '\n') { text++; len--; }
14272 commentList[index][--oldlen] = NULLCHAR;
14274 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14275 else strcat(commentList[index], "\n");
14276 strcat(commentList[index], text);
14277 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14278 else strcat(commentList[index], "\n");
14280 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14282 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14283 else commentList[index][0] = NULLCHAR;
14284 strcat(commentList[index], text);
14285 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14286 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14290 static char * FindStr( char * text, char * sub_text )
14292 char * result = strstr( text, sub_text );
14294 if( result != NULL ) {
14295 result += strlen( sub_text );
14301 /* [AS] Try to extract PV info from PGN comment */
14302 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14303 char *GetInfoFromComment( int index, char * text )
14305 char * sep = text, *p;
14307 if( text != NULL && index > 0 ) {
14310 int time = -1, sec = 0, deci;
14311 char * s_eval = FindStr( text, "[%eval " );
14312 char * s_emt = FindStr( text, "[%emt " );
14314 if( s_eval != NULL || s_emt != NULL ) {
14318 if( s_eval != NULL ) {
14319 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14323 if( delim != ']' ) {
14328 if( s_emt != NULL ) {
14333 /* We expect something like: [+|-]nnn.nn/dd */
14336 if(*text != '{') return text; // [HGM] braces: must be normal comment
14338 sep = strchr( text, '/' );
14339 if( sep == NULL || sep < (text+4) ) {
14344 if(p[1] == '(') { // comment starts with PV
14345 p = strchr(p, ')'); // locate end of PV
14346 if(p == NULL || sep < p+5) return text;
14347 // at this point we have something like "{(.*) +0.23/6 ..."
14348 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14349 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14350 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14352 time = -1; sec = -1; deci = -1;
14353 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14354 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14355 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14356 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14360 if( score_lo < 0 || score_lo >= 100 ) {
14364 if(sec >= 0) time = 600*time + 10*sec; else
14365 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14367 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14369 /* [HGM] PV time: now locate end of PV info */
14370 while( *++sep >= '0' && *sep <= '9'); // strip depth
14372 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14374 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14376 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14377 while(*sep == ' ') sep++;
14388 pvInfoList[index-1].depth = depth;
14389 pvInfoList[index-1].score = score;
14390 pvInfoList[index-1].time = 10*time; // centi-sec
14391 if(*sep == '}') *sep = 0; else *--sep = '{';
14392 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14398 SendToProgram(message, cps)
14400 ChessProgramState *cps;
14402 int count, outCount, error;
14405 if (cps->pr == NULL) return;
14408 if (appData.debugMode) {
14411 fprintf(debugFP, "%ld >%-6s: %s",
14412 SubtractTimeMarks(&now, &programStartTime),
14413 cps->which, message);
14416 count = strlen(message);
14417 outCount = OutputToProcess(cps->pr, message, count, &error);
14418 if (outCount < count && !exiting
14419 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14420 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14421 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14422 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14423 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14424 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14425 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14426 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14428 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14429 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14430 gameInfo.result = res;
14432 gameInfo.resultDetails = StrSave(buf);
14434 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14435 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14440 ReceiveFromProgram(isr, closure, message, count, error)
14441 InputSourceRef isr;
14449 ChessProgramState *cps = (ChessProgramState *)closure;
14451 if (isr != cps->isr) return; /* Killed intentionally */
14454 RemoveInputSource(cps->isr);
14455 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14456 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14457 _(cps->which), cps->program);
14458 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14459 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14460 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14461 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14462 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14464 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14465 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14466 gameInfo.result = res;
14468 gameInfo.resultDetails = StrSave(buf);
14470 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14471 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14473 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14474 _(cps->which), cps->program);
14475 RemoveInputSource(cps->isr);
14477 /* [AS] Program is misbehaving badly... kill it */
14478 if( count == -2 ) {
14479 DestroyChildProcess( cps->pr, 9 );
14483 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14488 if ((end_str = strchr(message, '\r')) != NULL)
14489 *end_str = NULLCHAR;
14490 if ((end_str = strchr(message, '\n')) != NULL)
14491 *end_str = NULLCHAR;
14493 if (appData.debugMode) {
14494 TimeMark now; int print = 1;
14495 char *quote = ""; char c; int i;
14497 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14498 char start = message[0];
14499 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14500 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14501 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14502 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14503 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14504 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14505 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14506 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14507 sscanf(message, "hint: %c", &c)!=1 &&
14508 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14509 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14510 print = (appData.engineComments >= 2);
14512 message[0] = start; // restore original message
14516 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14517 SubtractTimeMarks(&now, &programStartTime), cps->which,
14523 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14524 if (appData.icsEngineAnalyze) {
14525 if (strstr(message, "whisper") != NULL ||
14526 strstr(message, "kibitz") != NULL ||
14527 strstr(message, "tellics") != NULL) return;
14530 HandleMachineMove(message, cps);
14535 SendTimeControl(cps, mps, tc, inc, sd, st)
14536 ChessProgramState *cps;
14537 int mps, inc, sd, st;
14543 if( timeControl_2 > 0 ) {
14544 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14545 tc = timeControl_2;
14548 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14549 inc /= cps->timeOdds;
14550 st /= cps->timeOdds;
14552 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14555 /* Set exact time per move, normally using st command */
14556 if (cps->stKludge) {
14557 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14559 if (seconds == 0) {
14560 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14562 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14565 snprintf(buf, MSG_SIZ, "st %d\n", st);
14568 /* Set conventional or incremental time control, using level command */
14569 if (seconds == 0) {
14570 /* Note old gnuchess bug -- minutes:seconds used to not work.
14571 Fixed in later versions, but still avoid :seconds
14572 when seconds is 0. */
14573 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14575 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14576 seconds, inc/1000.);
14579 SendToProgram(buf, cps);
14581 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14582 /* Orthogonally, limit search to given depth */
14584 if (cps->sdKludge) {
14585 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14587 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14589 SendToProgram(buf, cps);
14592 if(cps->nps >= 0) { /* [HGM] nps */
14593 if(cps->supportsNPS == FALSE)
14594 cps->nps = -1; // don't use if engine explicitly says not supported!
14596 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14597 SendToProgram(buf, cps);
14602 ChessProgramState *WhitePlayer()
14603 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14605 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14606 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14612 SendTimeRemaining(cps, machineWhite)
14613 ChessProgramState *cps;
14614 int /*boolean*/ machineWhite;
14616 char message[MSG_SIZ];
14619 /* Note: this routine must be called when the clocks are stopped
14620 or when they have *just* been set or switched; otherwise
14621 it will be off by the time since the current tick started.
14623 if (machineWhite) {
14624 time = whiteTimeRemaining / 10;
14625 otime = blackTimeRemaining / 10;
14627 time = blackTimeRemaining / 10;
14628 otime = whiteTimeRemaining / 10;
14630 /* [HGM] translate opponent's time by time-odds factor */
14631 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14632 if (appData.debugMode) {
14633 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14636 if (time <= 0) time = 1;
14637 if (otime <= 0) otime = 1;
14639 snprintf(message, MSG_SIZ, "time %ld\n", time);
14640 SendToProgram(message, cps);
14642 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14643 SendToProgram(message, cps);
14647 BoolFeature(p, name, loc, cps)
14651 ChessProgramState *cps;
14654 int len = strlen(name);
14657 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14659 sscanf(*p, "%d", &val);
14661 while (**p && **p != ' ')
14663 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14664 SendToProgram(buf, cps);
14671 IntFeature(p, name, loc, cps)
14675 ChessProgramState *cps;
14678 int len = strlen(name);
14679 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14681 sscanf(*p, "%d", loc);
14682 while (**p && **p != ' ') (*p)++;
14683 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14684 SendToProgram(buf, cps);
14691 StringFeature(p, name, loc, cps)
14695 ChessProgramState *cps;
14698 int len = strlen(name);
14699 if (strncmp((*p), name, len) == 0
14700 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14702 sscanf(*p, "%[^\"]", loc);
14703 while (**p && **p != '\"') (*p)++;
14704 if (**p == '\"') (*p)++;
14705 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14706 SendToProgram(buf, cps);
14713 ParseOption(Option *opt, ChessProgramState *cps)
14714 // [HGM] options: process the string that defines an engine option, and determine
14715 // name, type, default value, and allowed value range
14717 char *p, *q, buf[MSG_SIZ];
14718 int n, min = (-1)<<31, max = 1<<31, def;
14720 if(p = strstr(opt->name, " -spin ")) {
14721 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14722 if(max < min) max = min; // enforce consistency
14723 if(def < min) def = min;
14724 if(def > max) def = max;
14729 } else if((p = strstr(opt->name, " -slider "))) {
14730 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14731 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14732 if(max < min) max = min; // enforce consistency
14733 if(def < min) def = min;
14734 if(def > max) def = max;
14738 opt->type = Spin; // Slider;
14739 } else if((p = strstr(opt->name, " -string "))) {
14740 opt->textValue = p+9;
14741 opt->type = TextBox;
14742 } else if((p = strstr(opt->name, " -file "))) {
14743 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14744 opt->textValue = p+7;
14745 opt->type = FileName; // FileName;
14746 } else if((p = strstr(opt->name, " -path "))) {
14747 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14748 opt->textValue = p+7;
14749 opt->type = PathName; // PathName;
14750 } else if(p = strstr(opt->name, " -check ")) {
14751 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14752 opt->value = (def != 0);
14753 opt->type = CheckBox;
14754 } else if(p = strstr(opt->name, " -combo ")) {
14755 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14756 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14757 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14758 opt->value = n = 0;
14759 while(q = StrStr(q, " /// ")) {
14760 n++; *q = 0; // count choices, and null-terminate each of them
14762 if(*q == '*') { // remember default, which is marked with * prefix
14766 cps->comboList[cps->comboCnt++] = q;
14768 cps->comboList[cps->comboCnt++] = NULL;
14770 opt->type = ComboBox;
14771 } else if(p = strstr(opt->name, " -button")) {
14772 opt->type = Button;
14773 } else if(p = strstr(opt->name, " -save")) {
14774 opt->type = SaveButton;
14775 } else return FALSE;
14776 *p = 0; // terminate option name
14777 // now look if the command-line options define a setting for this engine option.
14778 if(cps->optionSettings && cps->optionSettings[0])
14779 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14780 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14781 snprintf(buf, MSG_SIZ, "option %s", p);
14782 if(p = strstr(buf, ",")) *p = 0;
14783 if(q = strchr(buf, '=')) switch(opt->type) {
14785 for(n=0; n<opt->max; n++)
14786 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14789 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14793 opt->value = atoi(q+1);
14798 SendToProgram(buf, cps);
14804 FeatureDone(cps, val)
14805 ChessProgramState* cps;
14808 DelayedEventCallback cb = GetDelayedEvent();
14809 if ((cb == InitBackEnd3 && cps == &first) ||
14810 (cb == SettingsMenuIfReady && cps == &second) ||
14811 (cb == LoadEngine) ||
14812 (cb == TwoMachinesEventIfReady)) {
14813 CancelDelayedEvent();
14814 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14816 cps->initDone = val;
14819 /* Parse feature command from engine */
14821 ParseFeatures(args, cps)
14823 ChessProgramState *cps;
14831 while (*p == ' ') p++;
14832 if (*p == NULLCHAR) return;
14834 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14835 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14836 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14837 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14838 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14839 if (BoolFeature(&p, "reuse", &val, cps)) {
14840 /* Engine can disable reuse, but can't enable it if user said no */
14841 if (!val) cps->reuse = FALSE;
14844 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14845 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14846 if (gameMode == TwoMachinesPlay) {
14847 DisplayTwoMachinesTitle();
14853 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14854 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14855 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14856 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14857 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14858 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14859 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14860 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14861 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14862 if (IntFeature(&p, "done", &val, cps)) {
14863 FeatureDone(cps, val);
14866 /* Added by Tord: */
14867 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14868 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14869 /* End of additions by Tord */
14871 /* [HGM] added features: */
14872 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14873 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14874 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14875 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14876 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14877 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14878 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14879 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14880 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14881 SendToProgram(buf, cps);
14884 if(cps->nrOptions >= MAX_OPTIONS) {
14886 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14887 DisplayError(buf, 0);
14891 /* End of additions by HGM */
14893 /* unknown feature: complain and skip */
14895 while (*q && *q != '=') q++;
14896 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14897 SendToProgram(buf, cps);
14903 while (*p && *p != '\"') p++;
14904 if (*p == '\"') p++;
14906 while (*p && *p != ' ') p++;
14914 PeriodicUpdatesEvent(newState)
14917 if (newState == appData.periodicUpdates)
14920 appData.periodicUpdates=newState;
14922 /* Display type changes, so update it now */
14923 // DisplayAnalysis();
14925 /* Get the ball rolling again... */
14927 AnalysisPeriodicEvent(1);
14928 StartAnalysisClock();
14933 PonderNextMoveEvent(newState)
14936 if (newState == appData.ponderNextMove) return;
14937 if (gameMode == EditPosition) EditPositionDone(TRUE);
14939 SendToProgram("hard\n", &first);
14940 if (gameMode == TwoMachinesPlay) {
14941 SendToProgram("hard\n", &second);
14944 SendToProgram("easy\n", &first);
14945 thinkOutput[0] = NULLCHAR;
14946 if (gameMode == TwoMachinesPlay) {
14947 SendToProgram("easy\n", &second);
14950 appData.ponderNextMove = newState;
14954 NewSettingEvent(option, feature, command, value)
14956 int option, value, *feature;
14960 if (gameMode == EditPosition) EditPositionDone(TRUE);
14961 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14962 if(feature == NULL || *feature) SendToProgram(buf, &first);
14963 if (gameMode == TwoMachinesPlay) {
14964 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14969 ShowThinkingEvent()
14970 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14972 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14973 int newState = appData.showThinking
14974 // [HGM] thinking: other features now need thinking output as well
14975 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14977 if (oldState == newState) return;
14978 oldState = newState;
14979 if (gameMode == EditPosition) EditPositionDone(TRUE);
14981 SendToProgram("post\n", &first);
14982 if (gameMode == TwoMachinesPlay) {
14983 SendToProgram("post\n", &second);
14986 SendToProgram("nopost\n", &first);
14987 thinkOutput[0] = NULLCHAR;
14988 if (gameMode == TwoMachinesPlay) {
14989 SendToProgram("nopost\n", &second);
14992 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14996 AskQuestionEvent(title, question, replyPrefix, which)
14997 char *title; char *question; char *replyPrefix; char *which;
14999 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15000 if (pr == NoProc) return;
15001 AskQuestion(title, question, replyPrefix, pr);
15005 TypeInEvent(char firstChar)
15007 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
15008 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
15009 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
15010 gameMode == EditPosition || gameMode == IcsExamining ||
\r
15011 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
15012 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
15013 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
15014 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
15015 gameMode == Training) PopUpMoveDialog(firstChar);
15019 TypeInDoneEvent(char *move)
15022 int n, fromX, fromY, toX, toY;
15024 ChessMove moveType;
\r
15027 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
15028 EditPositionPasteFEN(move);
\r
15031 // [HGM] movenum: allow move number to be typed in any mode
\r
15032 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
15033 ToNrEvent(2*n-1);
\r
15037 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
15038 gameMode != Training) {
\r
15039 DisplayMoveError(_("Displayed move is not current"));
\r
15041 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15042 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
15043 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
15044 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
15045 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
15046 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
15048 DisplayMoveError(_("Could not parse move"));
\r
15054 DisplayMove(moveNumber)
15057 char message[MSG_SIZ];
15059 char cpThinkOutput[MSG_SIZ];
15061 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15063 if (moveNumber == forwardMostMove - 1 ||
15064 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15066 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15068 if (strchr(cpThinkOutput, '\n')) {
15069 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15072 *cpThinkOutput = NULLCHAR;
15075 /* [AS] Hide thinking from human user */
15076 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15077 *cpThinkOutput = NULLCHAR;
15078 if( thinkOutput[0] != NULLCHAR ) {
15081 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15082 cpThinkOutput[i] = '.';
15084 cpThinkOutput[i] = NULLCHAR;
15085 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15089 if (moveNumber == forwardMostMove - 1 &&
15090 gameInfo.resultDetails != NULL) {
15091 if (gameInfo.resultDetails[0] == NULLCHAR) {
15092 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15094 snprintf(res, MSG_SIZ, " {%s} %s",
15095 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15101 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15102 DisplayMessage(res, cpThinkOutput);
15104 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15105 WhiteOnMove(moveNumber) ? " " : ".. ",
15106 parseList[moveNumber], res);
15107 DisplayMessage(message, cpThinkOutput);
15112 DisplayComment(moveNumber, text)
15116 char title[MSG_SIZ];
15117 char buf[8000]; // comment can be long!
15120 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15121 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15123 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15124 WhiteOnMove(moveNumber) ? " " : ".. ",
15125 parseList[moveNumber]);
15127 // [HGM] PV info: display PV info together with (or as) comment
15128 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15129 if(text == NULL) text = "";
15130 score = pvInfoList[moveNumber].score;
15131 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15132 depth, (pvInfoList[moveNumber].time+50)/100, text);
15135 if (text != NULL && (appData.autoDisplayComment || commentUp))
15136 CommentPopUp(title, text);
15139 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15140 * might be busy thinking or pondering. It can be omitted if your
15141 * gnuchess is configured to stop thinking immediately on any user
15142 * input. However, that gnuchess feature depends on the FIONREAD
15143 * ioctl, which does not work properly on some flavors of Unix.
15147 ChessProgramState *cps;
15150 if (!cps->useSigint) return;
15151 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15152 switch (gameMode) {
15153 case MachinePlaysWhite:
15154 case MachinePlaysBlack:
15155 case TwoMachinesPlay:
15156 case IcsPlayingWhite:
15157 case IcsPlayingBlack:
15160 /* Skip if we know it isn't thinking */
15161 if (!cps->maybeThinking) return;
15162 if (appData.debugMode)
15163 fprintf(debugFP, "Interrupting %s\n", cps->which);
15164 InterruptChildProcess(cps->pr);
15165 cps->maybeThinking = FALSE;
15170 #endif /*ATTENTION*/
15176 if (whiteTimeRemaining <= 0) {
15179 if (appData.icsActive) {
15180 if (appData.autoCallFlag &&
15181 gameMode == IcsPlayingBlack && !blackFlag) {
15182 SendToICS(ics_prefix);
15183 SendToICS("flag\n");
15187 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15189 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15190 if (appData.autoCallFlag) {
15191 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15198 if (blackTimeRemaining <= 0) {
15201 if (appData.icsActive) {
15202 if (appData.autoCallFlag &&
15203 gameMode == IcsPlayingWhite && !whiteFlag) {
15204 SendToICS(ics_prefix);
15205 SendToICS("flag\n");
15209 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15211 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15212 if (appData.autoCallFlag) {
15213 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15226 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15227 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15230 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15232 if ( !WhiteOnMove(forwardMostMove) ) {
15233 /* White made time control */
15234 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15235 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15236 /* [HGM] time odds: correct new time quota for time odds! */
15237 / WhitePlayer()->timeOdds;
15238 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15240 lastBlack -= blackTimeRemaining;
15241 /* Black made time control */
15242 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15243 / WhitePlayer()->other->timeOdds;
15244 lastWhite = whiteTimeRemaining;
15249 DisplayBothClocks()
15251 int wom = gameMode == EditPosition ?
15252 !blackPlaysFirst : WhiteOnMove(currentMove);
15253 DisplayWhiteClock(whiteTimeRemaining, wom);
15254 DisplayBlackClock(blackTimeRemaining, !wom);
15258 /* Timekeeping seems to be a portability nightmare. I think everyone
15259 has ftime(), but I'm really not sure, so I'm including some ifdefs
15260 to use other calls if you don't. Clocks will be less accurate if
15261 you have neither ftime nor gettimeofday.
15264 /* VS 2008 requires the #include outside of the function */
15265 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15266 #include <sys/timeb.h>
15269 /* Get the current time as a TimeMark */
15274 #if HAVE_GETTIMEOFDAY
15276 struct timeval timeVal;
15277 struct timezone timeZone;
15279 gettimeofday(&timeVal, &timeZone);
15280 tm->sec = (long) timeVal.tv_sec;
15281 tm->ms = (int) (timeVal.tv_usec / 1000L);
15283 #else /*!HAVE_GETTIMEOFDAY*/
15286 // include <sys/timeb.h> / moved to just above start of function
15287 struct timeb timeB;
15290 tm->sec = (long) timeB.time;
15291 tm->ms = (int) timeB.millitm;
15293 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15294 tm->sec = (long) time(NULL);
15300 /* Return the difference in milliseconds between two
15301 time marks. We assume the difference will fit in a long!
15304 SubtractTimeMarks(tm2, tm1)
15305 TimeMark *tm2, *tm1;
15307 return 1000L*(tm2->sec - tm1->sec) +
15308 (long) (tm2->ms - tm1->ms);
15313 * Code to manage the game clocks.
15315 * In tournament play, black starts the clock and then white makes a move.
15316 * We give the human user a slight advantage if he is playing white---the
15317 * clocks don't run until he makes his first move, so it takes zero time.
15318 * Also, we don't account for network lag, so we could get out of sync
15319 * with GNU Chess's clock -- but then, referees are always right.
15322 static TimeMark tickStartTM;
15323 static long intendedTickLength;
15326 NextTickLength(timeRemaining)
15327 long timeRemaining;
15329 long nominalTickLength, nextTickLength;
15331 if (timeRemaining > 0L && timeRemaining <= 10000L)
15332 nominalTickLength = 100L;
15334 nominalTickLength = 1000L;
15335 nextTickLength = timeRemaining % nominalTickLength;
15336 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15338 return nextTickLength;
15341 /* Adjust clock one minute up or down */
15343 AdjustClock(Boolean which, int dir)
15345 if(which) blackTimeRemaining += 60000*dir;
15346 else whiteTimeRemaining += 60000*dir;
15347 DisplayBothClocks();
15350 /* Stop clocks and reset to a fresh time control */
15354 (void) StopClockTimer();
15355 if (appData.icsActive) {
15356 whiteTimeRemaining = blackTimeRemaining = 0;
15357 } else if (searchTime) {
15358 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15359 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15360 } else { /* [HGM] correct new time quote for time odds */
15361 whiteTC = blackTC = fullTimeControlString;
15362 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15363 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15365 if (whiteFlag || blackFlag) {
15367 whiteFlag = blackFlag = FALSE;
15369 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15370 DisplayBothClocks();
15373 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15375 /* Decrement running clock by amount of time that has passed */
15379 long timeRemaining;
15380 long lastTickLength, fudge;
15383 if (!appData.clockMode) return;
15384 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15388 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15390 /* Fudge if we woke up a little too soon */
15391 fudge = intendedTickLength - lastTickLength;
15392 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15394 if (WhiteOnMove(forwardMostMove)) {
15395 if(whiteNPS >= 0) lastTickLength = 0;
15396 timeRemaining = whiteTimeRemaining -= lastTickLength;
15397 if(timeRemaining < 0 && !appData.icsActive) {
15398 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15399 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15400 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15401 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15404 DisplayWhiteClock(whiteTimeRemaining - fudge,
15405 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15407 if(blackNPS >= 0) lastTickLength = 0;
15408 timeRemaining = blackTimeRemaining -= lastTickLength;
15409 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15410 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15412 blackStartMove = forwardMostMove;
15413 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15416 DisplayBlackClock(blackTimeRemaining - fudge,
15417 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15419 if (CheckFlags()) return;
15422 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15423 StartClockTimer(intendedTickLength);
15425 /* if the time remaining has fallen below the alarm threshold, sound the
15426 * alarm. if the alarm has sounded and (due to a takeback or time control
15427 * with increment) the time remaining has increased to a level above the
15428 * threshold, reset the alarm so it can sound again.
15431 if (appData.icsActive && appData.icsAlarm) {
15433 /* make sure we are dealing with the user's clock */
15434 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15435 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15438 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15439 alarmSounded = FALSE;
15440 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15442 alarmSounded = TRUE;
15448 /* A player has just moved, so stop the previously running
15449 clock and (if in clock mode) start the other one.
15450 We redisplay both clocks in case we're in ICS mode, because
15451 ICS gives us an update to both clocks after every move.
15452 Note that this routine is called *after* forwardMostMove
15453 is updated, so the last fractional tick must be subtracted
15454 from the color that is *not* on move now.
15457 SwitchClocks(int newMoveNr)
15459 long lastTickLength;
15461 int flagged = FALSE;
15465 if (StopClockTimer() && appData.clockMode) {
15466 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15467 if (!WhiteOnMove(forwardMostMove)) {
15468 if(blackNPS >= 0) lastTickLength = 0;
15469 blackTimeRemaining -= lastTickLength;
15470 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15471 // if(pvInfoList[forwardMostMove].time == -1)
15472 pvInfoList[forwardMostMove].time = // use GUI time
15473 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15475 if(whiteNPS >= 0) lastTickLength = 0;
15476 whiteTimeRemaining -= lastTickLength;
15477 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15478 // if(pvInfoList[forwardMostMove].time == -1)
15479 pvInfoList[forwardMostMove].time =
15480 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15482 flagged = CheckFlags();
15484 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15485 CheckTimeControl();
15487 if (flagged || !appData.clockMode) return;
15489 switch (gameMode) {
15490 case MachinePlaysBlack:
15491 case MachinePlaysWhite:
15492 case BeginningOfGame:
15493 if (pausing) return;
15497 case PlayFromGameFile:
15505 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15506 if(WhiteOnMove(forwardMostMove))
15507 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15508 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15512 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15513 whiteTimeRemaining : blackTimeRemaining);
15514 StartClockTimer(intendedTickLength);
15518 /* Stop both clocks */
15522 long lastTickLength;
15525 if (!StopClockTimer()) return;
15526 if (!appData.clockMode) return;
15530 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15531 if (WhiteOnMove(forwardMostMove)) {
15532 if(whiteNPS >= 0) lastTickLength = 0;
15533 whiteTimeRemaining -= lastTickLength;
15534 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15536 if(blackNPS >= 0) lastTickLength = 0;
15537 blackTimeRemaining -= lastTickLength;
15538 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15543 /* Start clock of player on move. Time may have been reset, so
15544 if clock is already running, stop and restart it. */
15548 (void) StopClockTimer(); /* in case it was running already */
15549 DisplayBothClocks();
15550 if (CheckFlags()) return;
15552 if (!appData.clockMode) return;
15553 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15555 GetTimeMark(&tickStartTM);
15556 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15557 whiteTimeRemaining : blackTimeRemaining);
15559 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15560 whiteNPS = blackNPS = -1;
15561 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15562 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15563 whiteNPS = first.nps;
15564 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15565 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15566 blackNPS = first.nps;
15567 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15568 whiteNPS = second.nps;
15569 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15570 blackNPS = second.nps;
15571 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15573 StartClockTimer(intendedTickLength);
15580 long second, minute, hour, day;
15582 static char buf[32];
15584 if (ms > 0 && ms <= 9900) {
15585 /* convert milliseconds to tenths, rounding up */
15586 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15588 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15592 /* convert milliseconds to seconds, rounding up */
15593 /* use floating point to avoid strangeness of integer division
15594 with negative dividends on many machines */
15595 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15602 day = second / (60 * 60 * 24);
15603 second = second % (60 * 60 * 24);
15604 hour = second / (60 * 60);
15605 second = second % (60 * 60);
15606 minute = second / 60;
15607 second = second % 60;
15610 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15611 sign, day, hour, minute, second);
15613 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15615 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15622 * This is necessary because some C libraries aren't ANSI C compliant yet.
15625 StrStr(string, match)
15626 char *string, *match;
15630 length = strlen(match);
15632 for (i = strlen(string) - length; i >= 0; i--, string++)
15633 if (!strncmp(match, string, length))
15640 StrCaseStr(string, match)
15641 char *string, *match;
15645 length = strlen(match);
15647 for (i = strlen(string) - length; i >= 0; i--, string++) {
15648 for (j = 0; j < length; j++) {
15649 if (ToLower(match[j]) != ToLower(string[j]))
15652 if (j == length) return string;
15666 c1 = ToLower(*s1++);
15667 c2 = ToLower(*s2++);
15668 if (c1 > c2) return 1;
15669 if (c1 < c2) return -1;
15670 if (c1 == NULLCHAR) return 0;
15679 return isupper(c) ? tolower(c) : c;
15687 return islower(c) ? toupper(c) : c;
15689 #endif /* !_amigados */
15697 if ((ret = (char *) malloc(strlen(s) + 1)))
15699 safeStrCpy(ret, s, strlen(s)+1);
15705 StrSavePtr(s, savePtr)
15706 char *s, **savePtr;
15711 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15712 safeStrCpy(*savePtr, s, strlen(s)+1);
15724 clock = time((time_t *)NULL);
15725 tm = localtime(&clock);
15726 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15727 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15728 return StrSave(buf);
15733 PositionToFEN(move, overrideCastling)
15735 char *overrideCastling;
15737 int i, j, fromX, fromY, toX, toY;
15744 whiteToPlay = (gameMode == EditPosition) ?
15745 !blackPlaysFirst : (move % 2 == 0);
15748 /* Piece placement data */
15749 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15751 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15752 if (boards[move][i][j] == EmptySquare) {
15754 } else { ChessSquare piece = boards[move][i][j];
15755 if (emptycount > 0) {
15756 if(emptycount<10) /* [HGM] can be >= 10 */
15757 *p++ = '0' + emptycount;
15758 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15761 if(PieceToChar(piece) == '+') {
15762 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15764 piece = (ChessSquare)(DEMOTED piece);
15766 *p++ = PieceToChar(piece);
15768 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15769 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15774 if (emptycount > 0) {
15775 if(emptycount<10) /* [HGM] can be >= 10 */
15776 *p++ = '0' + emptycount;
15777 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15784 /* [HGM] print Crazyhouse or Shogi holdings */
15785 if( gameInfo.holdingsWidth ) {
15786 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15788 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15789 piece = boards[move][i][BOARD_WIDTH-1];
15790 if( piece != EmptySquare )
15791 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15792 *p++ = PieceToChar(piece);
15794 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15795 piece = boards[move][BOARD_HEIGHT-i-1][0];
15796 if( piece != EmptySquare )
15797 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15798 *p++ = PieceToChar(piece);
15801 if( q == p ) *p++ = '-';
15807 *p++ = whiteToPlay ? 'w' : 'b';
15810 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15811 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15813 if(nrCastlingRights) {
15815 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15816 /* [HGM] write directly from rights */
15817 if(boards[move][CASTLING][2] != NoRights &&
15818 boards[move][CASTLING][0] != NoRights )
15819 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15820 if(boards[move][CASTLING][2] != NoRights &&
15821 boards[move][CASTLING][1] != NoRights )
15822 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15823 if(boards[move][CASTLING][5] != NoRights &&
15824 boards[move][CASTLING][3] != NoRights )
15825 *p++ = boards[move][CASTLING][3] + AAA;
15826 if(boards[move][CASTLING][5] != NoRights &&
15827 boards[move][CASTLING][4] != NoRights )
15828 *p++ = boards[move][CASTLING][4] + AAA;
15831 /* [HGM] write true castling rights */
15832 if( nrCastlingRights == 6 ) {
15833 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15834 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15835 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15836 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15837 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15838 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15839 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15840 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15843 if (q == p) *p++ = '-'; /* No castling rights */
15847 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15848 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15849 /* En passant target square */
15850 if (move > backwardMostMove) {
15851 fromX = moveList[move - 1][0] - AAA;
15852 fromY = moveList[move - 1][1] - ONE;
15853 toX = moveList[move - 1][2] - AAA;
15854 toY = moveList[move - 1][3] - ONE;
15855 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15856 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15857 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15859 /* 2-square pawn move just happened */
15861 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15865 } else if(move == backwardMostMove) {
15866 // [HGM] perhaps we should always do it like this, and forget the above?
15867 if((signed char)boards[move][EP_STATUS] >= 0) {
15868 *p++ = boards[move][EP_STATUS] + AAA;
15869 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15880 /* [HGM] find reversible plies */
15881 { int i = 0, j=move;
15883 if (appData.debugMode) { int k;
15884 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15885 for(k=backwardMostMove; k<=forwardMostMove; k++)
15886 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15890 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15891 if( j == backwardMostMove ) i += initialRulePlies;
15892 sprintf(p, "%d ", i);
15893 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15895 /* Fullmove number */
15896 sprintf(p, "%d", (move / 2) + 1);
15898 return StrSave(buf);
15902 ParseFEN(board, blackPlaysFirst, fen)
15904 int *blackPlaysFirst;
15914 /* [HGM] by default clear Crazyhouse holdings, if present */
15915 if(gameInfo.holdingsWidth) {
15916 for(i=0; i<BOARD_HEIGHT; i++) {
15917 board[i][0] = EmptySquare; /* black holdings */
15918 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15919 board[i][1] = (ChessSquare) 0; /* black counts */
15920 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15924 /* Piece placement data */
15925 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15928 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15929 if (*p == '/') p++;
15930 emptycount = gameInfo.boardWidth - j;
15931 while (emptycount--)
15932 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15934 #if(BOARD_FILES >= 10)
15935 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15936 p++; emptycount=10;
15937 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15938 while (emptycount--)
15939 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15941 } else if (isdigit(*p)) {
15942 emptycount = *p++ - '0';
15943 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15944 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15945 while (emptycount--)
15946 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15947 } else if (*p == '+' || isalpha(*p)) {
15948 if (j >= gameInfo.boardWidth) return FALSE;
15950 piece = CharToPiece(*++p);
15951 if(piece == EmptySquare) return FALSE; /* unknown piece */
15952 piece = (ChessSquare) (PROMOTED piece ); p++;
15953 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15954 } else piece = CharToPiece(*p++);
15956 if(piece==EmptySquare) return FALSE; /* unknown piece */
15957 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15958 piece = (ChessSquare) (PROMOTED piece);
15959 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15962 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15968 while (*p == '/' || *p == ' ') p++;
15970 /* [HGM] look for Crazyhouse holdings here */
15971 while(*p==' ') p++;
15972 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15974 if(*p == '-' ) p++; /* empty holdings */ else {
15975 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15976 /* if we would allow FEN reading to set board size, we would */
15977 /* have to add holdings and shift the board read so far here */
15978 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15980 if((int) piece >= (int) BlackPawn ) {
15981 i = (int)piece - (int)BlackPawn;
15982 i = PieceToNumber((ChessSquare)i);
15983 if( i >= gameInfo.holdingsSize ) return FALSE;
15984 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15985 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15987 i = (int)piece - (int)WhitePawn;
15988 i = PieceToNumber((ChessSquare)i);
15989 if( i >= gameInfo.holdingsSize ) return FALSE;
15990 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15991 board[i][BOARD_WIDTH-2]++; /* black holdings */
15998 while(*p == ' ') p++;
16002 if(appData.colorNickNames) {
16003 if( c == appData.colorNickNames[0] ) c = 'w'; else
16004 if( c == appData.colorNickNames[1] ) c = 'b';
16008 *blackPlaysFirst = FALSE;
16011 *blackPlaysFirst = TRUE;
16017 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16018 /* return the extra info in global variiables */
16020 /* set defaults in case FEN is incomplete */
16021 board[EP_STATUS] = EP_UNKNOWN;
16022 for(i=0; i<nrCastlingRights; i++ ) {
16023 board[CASTLING][i] =
16024 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16025 } /* assume possible unless obviously impossible */
16026 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16027 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16028 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16029 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16030 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16031 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16032 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16033 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16036 while(*p==' ') p++;
16037 if(nrCastlingRights) {
16038 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16039 /* castling indicator present, so default becomes no castlings */
16040 for(i=0; i<nrCastlingRights; i++ ) {
16041 board[CASTLING][i] = NoRights;
16044 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16045 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16046 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16047 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16048 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16050 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16051 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16052 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16054 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16055 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16056 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16057 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16058 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16059 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16062 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16063 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16064 board[CASTLING][2] = whiteKingFile;
16067 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16068 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16069 board[CASTLING][2] = whiteKingFile;
16072 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16073 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16074 board[CASTLING][5] = blackKingFile;
16077 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16078 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16079 board[CASTLING][5] = blackKingFile;
16082 default: /* FRC castlings */
16083 if(c >= 'a') { /* black rights */
16084 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16085 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16086 if(i == BOARD_RGHT) break;
16087 board[CASTLING][5] = i;
16089 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16090 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16092 board[CASTLING][3] = c;
16094 board[CASTLING][4] = c;
16095 } else { /* white rights */
16096 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16097 if(board[0][i] == WhiteKing) break;
16098 if(i == BOARD_RGHT) break;
16099 board[CASTLING][2] = i;
16100 c -= AAA - 'a' + 'A';
16101 if(board[0][c] >= WhiteKing) break;
16103 board[CASTLING][0] = c;
16105 board[CASTLING][1] = c;
16109 for(i=0; i<nrCastlingRights; i++)
16110 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16111 if (appData.debugMode) {
16112 fprintf(debugFP, "FEN castling rights:");
16113 for(i=0; i<nrCastlingRights; i++)
16114 fprintf(debugFP, " %d", board[CASTLING][i]);
16115 fprintf(debugFP, "\n");
16118 while(*p==' ') p++;
16121 /* read e.p. field in games that know e.p. capture */
16122 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16123 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16125 p++; board[EP_STATUS] = EP_NONE;
16127 char c = *p++ - AAA;
16129 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16130 if(*p >= '0' && *p <='9') p++;
16131 board[EP_STATUS] = c;
16136 if(sscanf(p, "%d", &i) == 1) {
16137 FENrulePlies = i; /* 50-move ply counter */
16138 /* (The move number is still ignored) */
16145 EditPositionPasteFEN(char *fen)
16148 Board initial_position;
16150 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16151 DisplayError(_("Bad FEN position in clipboard"), 0);
16154 int savedBlackPlaysFirst = blackPlaysFirst;
16155 EditPositionEvent();
16156 blackPlaysFirst = savedBlackPlaysFirst;
16157 CopyBoard(boards[0], initial_position);
16158 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16159 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16160 DisplayBothClocks();
16161 DrawPosition(FALSE, boards[currentMove]);
16166 static char cseq[12] = "\\ ";
16168 Boolean set_cont_sequence(char *new_seq)
16173 // handle bad attempts to set the sequence
16175 return 0; // acceptable error - no debug
16177 len = strlen(new_seq);
16178 ret = (len > 0) && (len < sizeof(cseq));
16180 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16181 else if (appData.debugMode)
16182 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16187 reformat a source message so words don't cross the width boundary. internal
16188 newlines are not removed. returns the wrapped size (no null character unless
16189 included in source message). If dest is NULL, only calculate the size required
16190 for the dest buffer. lp argument indicats line position upon entry, and it's
16191 passed back upon exit.
16193 int wrap(char *dest, char *src, int count, int width, int *lp)
16195 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16197 cseq_len = strlen(cseq);
16198 old_line = line = *lp;
16199 ansi = len = clen = 0;
16201 for (i=0; i < count; i++)
16203 if (src[i] == '\033')
16206 // if we hit the width, back up
16207 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16209 // store i & len in case the word is too long
16210 old_i = i, old_len = len;
16212 // find the end of the last word
16213 while (i && src[i] != ' ' && src[i] != '\n')
16219 // word too long? restore i & len before splitting it
16220 if ((old_i-i+clen) >= width)
16227 if (i && src[i-1] == ' ')
16230 if (src[i] != ' ' && src[i] != '\n')
16237 // now append the newline and continuation sequence
16242 strncpy(dest+len, cseq, cseq_len);
16250 dest[len] = src[i];
16254 if (src[i] == '\n')
16259 if (dest && appData.debugMode)
16261 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16262 count, width, line, len, *lp);
16263 show_bytes(debugFP, src, count);
16264 fprintf(debugFP, "\ndest: ");
16265 show_bytes(debugFP, dest, len);
16266 fprintf(debugFP, "\n");
16268 *lp = dest ? line : old_line;
16273 // [HGM] vari: routines for shelving variations
16276 PushInner(int firstMove, int lastMove)
16278 int i, j, nrMoves = lastMove - firstMove;
16280 // push current tail of game on stack
16281 savedResult[storedGames] = gameInfo.result;
16282 savedDetails[storedGames] = gameInfo.resultDetails;
16283 gameInfo.resultDetails = NULL;
16284 savedFirst[storedGames] = firstMove;
16285 savedLast [storedGames] = lastMove;
16286 savedFramePtr[storedGames] = framePtr;
16287 framePtr -= nrMoves; // reserve space for the boards
16288 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16289 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16290 for(j=0; j<MOVE_LEN; j++)
16291 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16292 for(j=0; j<2*MOVE_LEN; j++)
16293 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16294 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16295 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16296 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16297 pvInfoList[firstMove+i-1].depth = 0;
16298 commentList[framePtr+i] = commentList[firstMove+i];
16299 commentList[firstMove+i] = NULL;
16303 forwardMostMove = firstMove; // truncate game so we can start variation
16307 PushTail(int firstMove, int lastMove)
16309 if(appData.icsActive) { // only in local mode
16310 forwardMostMove = currentMove; // mimic old ICS behavior
16313 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16315 PushInner(firstMove, lastMove);
16316 if(storedGames == 1) GreyRevert(FALSE);
16320 PopInner(Boolean annotate)
16323 char buf[8000], moveBuf[20];
16326 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16327 nrMoves = savedLast[storedGames] - currentMove;
16330 if(!WhiteOnMove(currentMove))
16331 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16332 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16333 for(i=currentMove; i<forwardMostMove; i++) {
16335 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16336 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16337 strcat(buf, moveBuf);
16338 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16339 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16343 for(i=1; i<=nrMoves; i++) { // copy last variation back
16344 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16345 for(j=0; j<MOVE_LEN; j++)
16346 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16347 for(j=0; j<2*MOVE_LEN; j++)
16348 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16349 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16350 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16351 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16352 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16353 commentList[currentMove+i] = commentList[framePtr+i];
16354 commentList[framePtr+i] = NULL;
16356 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16357 framePtr = savedFramePtr[storedGames];
16358 gameInfo.result = savedResult[storedGames];
16359 if(gameInfo.resultDetails != NULL) {
16360 free(gameInfo.resultDetails);
16362 gameInfo.resultDetails = savedDetails[storedGames];
16363 forwardMostMove = currentMove + nrMoves;
16367 PopTail(Boolean annotate)
16369 if(appData.icsActive) return FALSE; // only in local mode
16370 if(!storedGames) return FALSE; // sanity
16371 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16373 PopInner(annotate);
16375 if(storedGames == 0) GreyRevert(TRUE);
16381 { // remove all shelved variations
16383 for(i=0; i<storedGames; i++) {
16384 if(savedDetails[i])
16385 free(savedDetails[i]);
16386 savedDetails[i] = NULL;
16388 for(i=framePtr; i<MAX_MOVES; i++) {
16389 if(commentList[i]) free(commentList[i]);
16390 commentList[i] = NULL;
16392 framePtr = MAX_MOVES-1;
16397 LoadVariation(int index, char *text)
16398 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16399 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16400 int level = 0, move;
16402 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16403 // first find outermost bracketing variation
16404 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16405 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16406 if(*p == '{') wait = '}'; else
16407 if(*p == '[') wait = ']'; else
16408 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16409 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16411 if(*p == wait) wait = NULLCHAR; // closing ]} found
16414 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16415 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16416 end[1] = NULLCHAR; // clip off comment beyond variation
16417 ToNrEvent(currentMove-1);
16418 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16419 // kludge: use ParsePV() to append variation to game
16420 move = currentMove;
16421 ParsePV(start, TRUE, TRUE);
16422 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16423 ClearPremoveHighlights();
16425 ToNrEvent(currentMove+1);